From 4e827f65f8d0c086215af9f9e50b471ff86e103e Mon Sep 17 00:00:00 2001 From: Isha Sovasaria Date: Sun, 2 Nov 2025 12:09:20 +0800 Subject: [PATCH 1/3] Add parser updates and chunking logic --- parser/chapter1_chunks.json | 2801 +++++++ parser/chapter2_chunks.json | 1352 ++++ parser/chapter3_chunks.json | 2378 ++++++ parser/chapter4_chunks.json | 8786 ++++++++++++++++++++++ parser/chapter5_chunks.json | 2468 ++++++ parser/chunking.py | 238 + parser/parse_sicp.py | 116 + parser/sicp_mesochunks_semantic_rag.json | 1250 +++ 8 files changed, 19389 insertions(+) create mode 100644 parser/chapter1_chunks.json create mode 100644 parser/chapter2_chunks.json create mode 100644 parser/chapter3_chunks.json create mode 100644 parser/chapter4_chunks.json create mode 100644 parser/chapter5_chunks.json create mode 100644 parser/chunking.py create mode 100644 parser/parse_sicp.py create mode 100644 parser/sicp_mesochunks_semantic_rag.json diff --git a/parser/chapter1_chunks.json b/parser/chapter1_chunks.json new file mode 100644 index 000000000..ae9866951 --- /dev/null +++ b/parser/chapter1_chunks.json @@ -0,0 +1,2801 @@ +[ + { + "source_file": "chapter1.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Procedures Functions", + "parent_title": null, + "depth": 0, + "paragraph_index": 1, + "content": "We are about to study the idea of a computational process . data . program . People create programs to direct processes. In effect, we conjure the spirits of the computer with our spells." + }, + { + "source_file": "chapter1.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Procedures Functions", + "parent_title": null, + "depth": 0, + "paragraph_index": 2, + "content": "A computational process is indeed much like a sorcerer s idea of a spirit. It cannot be seen or touched. It is not composed of matter at all. However, it is very real. It can perform intellectual work. It can answer questions. It can affect the world by disbursing money at a bank or by controlling a robot arm in a factory. The programs we use to conjure processes are like a sorcerer s spells. They are carefully composed from symbolic expressions in arcane and esoteric programming languages" + }, + { + "source_file": "chapter1.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Procedures Functions", + "parent_title": null, + "depth": 0, + "paragraph_index": 3, + "content": "A computational process, in a correctly working computer, executes programs precisely and accurately. Thus, like the sorcerer s apprentice, novice programmers must learn to understand and to anticipate the consequences of their conjuring. Even small errors (usually called bugs or glitches ) (usually called bugs ) in programs can have complex and unanticipated consequences." + }, + { + "source_file": "chapter1.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Procedures Functions", + "parent_title": null, + "depth": 0, + "paragraph_index": 4, + "content": "Fortunately, learning to program is considerably less dangerous than learning sorcery, because the spirits we deal with are conveniently contained in a secure way. Real-world programming, however, requires care, expertise, and wisdom. A small bug in a computer-aided design program, for example, can lead to the catastrophic collapse of an airplane or a dam or the self-destruction of an industrial robot." + }, + { + "source_file": "chapter1.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Procedures Functions", + "parent_title": null, + "depth": 0, + "paragraph_index": 5, + "content": "Master software engineers have the ability to organize programs so that they can be reasonably sure that the resulting processes will perform the tasks intended. They can visualize the behavior of their systems in advance. They know how to structure programs so that unanticipated problems do not lead to catastrophic consequences, and when problems do arise, they can debug their programs. Well-designed computational systems, like well-designed automobiles or nuclear reactors, are designed in a modular manner, so that the parts can be constructed, replaced, and debugged separately." + }, + { + "source_file": "chapter1.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Procedures Functions", + "parent_title": null, + "depth": 0, + "paragraph_index": 6, + "content": "We need an appropriate language for describing processes, and we will use for this purpose the programming language Lisp. Just as our everyday thoughts are usually expressed in our natural language (such as English, Swedish, or German), and descriptions of quantitative phenomena are expressed with mathematical notations, our procedural thoughts will be expressed in Lisp. recursion equations , as a model for computation. The language was conceived by Recursive Functions of Symbolic Expressions and Their Computation by Machine (" + }, + { + "source_file": "chapter1.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Procedures Functions", + "parent_title": null, + "depth": 0, + "paragraph_index": 7, + "content": "We need an appropriate language for describing processes, and we will use for this purpose the programming language JavaScript. Just as our everyday thoughts are usually expressed in our natural language (such as English, Swedish, or Chinese), and descriptions of quantitative phenomena are expressed with mathematical notations, our procedural thoughts will be expressed in JavaScript. Mocha , which was later renamed to LiveScript , and finally to JavaScript. JavaScript is a trademark of Oracle Corporation." + }, + { + "source_file": "chapter1.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Procedures Functions", + "parent_title": null, + "depth": 0, + "paragraph_index": 8, + "content": "Despite its inception as a mathematical formalism, Lisp is a practical programming language. A Lisp interpreter is a machine that carries out processes described in the Lisp language. The first Lisp interpreter was implemented by" + }, + { + "source_file": "chapter1.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Procedures Functions", + "parent_title": null, + "depth": 0, + "paragraph_index": 9, + "content": "Despite its inception as a language for scripting the web, JavaScript interpreter is a machine that carries out processes described in the JavaScript language." + }, + { + "source_file": "chapter1.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Procedures Functions", + "parent_title": null, + "depth": 0, + "paragraph_index": 10, + "content": "Lisp was not the product of a concerted design effort. Instead, it evolved informally in an experimental manner in response to users needs and to pragmatic implementation considerations. Lisp s informal evolution has continued through the years, and the community of Lisp users has traditionally resisted attempts to promulgate any official definition of the language. This evolution, together with the flexibility and elegance of the initial conception, has enabled Lisp, which is the second oldest language in widespread use today (only" + }, + { + "source_file": "chapter1.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Procedures Functions", + "parent_title": null, + "depth": 0, + "paragraph_index": 11, + "content": "JavaScript bears only superficial resemblance to the language Java, after which it was (eventually) named; both Java and JavaScript use the block structure of the language C. In contrast with Java and C, which usually employ compilation to lower-level languages, JavaScript programs were initially interpreted by web browsers. s Internet Explorer, whose JavaScript version is called JScript . The popularity of JavaScript for controlling web browsers gave rise to a standardization effort, culminating in ECMAScript . The and completed in June 1997 (" + }, + { + "source_file": "chapter1.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Procedures Functions", + "parent_title": null, + "depth": 0, + "paragraph_index": 12, + "content": "Because of its experimental character and its emphasis on symbol manipulation," + }, + { + "source_file": "chapter1.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Procedures Functions", + "parent_title": null, + "depth": 0, + "paragraph_index": 13, + "content": "The practice of embedding JavaScript programs in web pages encouraged the developers of web browsers to implement JavaScript interpreters. As these programs became more complex, the interpreters became more efficient in executing them, eventually using sophisticated implementation techniques such as Just-In-Time (JIT) compilation. The majority of JavaScript programs as of this writing (2021) are embedded in web pages and interpreted by browsers, but JavaScript is increasingly used as a general-purpose programming language, using systems such as Node.js." + }, + { + "source_file": "chapter1.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Procedures Functions", + "parent_title": null, + "depth": 0, + "paragraph_index": 14, + "content": "If Lisp is not a mainstream language, why are we using it as the framework for our discussion of programming? Because the language possesses procedures , can themselves be represented and manipulated as Lisp data. The importance of this is that there are powerful program-design techniques that rely on the ability to blur the traditional distinction between passive data and active processes. As we shall discover, Lisp s flexibility in handling procedures as data makes it one of the most convenient languages in existence for exploring these techniques. The ability to represent procedures as data also makes Lisp an excellent language for writing programs that must manipulate other programs as data, such as the interpreters and compilers that support computer languages. Above and beyond these considerations, programming in Lisp is great fun." + }, + { + "source_file": "section1.xml", + "tag_type": "SECTION", + "title": "The Elements of Programming", + "parent_title": "Building Abstractions with Procedures Functions", + "depth": 1, + "paragraph_index": 1, + "content": "A powerful 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. Thus, 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: primitive expressions , cerned with, means of combination , by means of abstraction ," + }, + { + "source_file": "section1.xml", + "tag_type": "SECTION", + "title": "The Elements of Programming", + "parent_title": "Building Abstractions with Procedures Functions", + "depth": 1, + "paragraph_index": 2, + "content": "In programming, we deal with two kinds of elements: and stuff that we want to manipulate, and procedures functions are descriptions of the rules for manipulating the data. Thus, any powerful programming language should be able to describe primitive data and primitive procedures functions and should have methods for combining and abstracting procedures functions and data." + }, + { + "source_file": "section1.xml", + "tag_type": "SECTION", + "title": "The Elements of Programming", + "parent_title": "Building Abstractions with Procedures Functions", + "depth": 1, + "paragraph_index": 3, + "content": "In this chapter we will deal only with simple procedures functions . procedures functions to manipulate compound data as well." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "section1.xml", + "tag_type": "SECTION", + "title": "The Elements of Programming", + "parent_title": "Building Abstractions with Procedures Functions", + "depth": 1, + "paragraph_index": 1, + "content": "A powerful 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. Thus, 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: primitive expressions , cerned with, means of combination , by means of abstraction ," + }, + { + "source_file": "section1.xml", + "tag_type": "SECTION", + "title": "The Elements of Programming", + "parent_title": "Building Abstractions with Procedures Functions", + "depth": 1, + "paragraph_index": 2, + "content": "In programming, we deal with two kinds of elements: and stuff that we want to manipulate, and procedures functions are descriptions of the rules for manipulating the data. Thus, any powerful programming language should be able to describe primitive data and primitive procedures functions and should have methods for combining and abstracting procedures functions and data." + }, + { + "source_file": "section1.xml", + "tag_type": "SECTION", + "title": "The Elements of Programming", + "parent_title": "Building Abstractions with Procedures Functions", + "depth": 1, + "paragraph_index": 3, + "content": "In this chapter we will deal only with simple procedures functions . procedures functions to manipulate compound data as well." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "section1.xml", + "tag_type": "SECTION", + "title": "The Elements of Programming", + "parent_title": "Building Abstractions with Procedures Functions", + "depth": 1, + "paragraph_index": 1, + "content": "A powerful 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. Thus, 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: primitive expressions , cerned with, means of combination , by means of abstraction ," + }, + { + "source_file": "section1.xml", + "tag_type": "SECTION", + "title": "The Elements of Programming", + "parent_title": "Building Abstractions with Procedures Functions", + "depth": 1, + "paragraph_index": 2, + "content": "In programming, we deal with two kinds of elements: and stuff that we want to manipulate, and procedures functions are descriptions of the rules for manipulating the data. Thus, any powerful programming language should be able to describe primitive data and primitive procedures functions and should have methods for combining and abstracting procedures functions and data." + }, + { + "source_file": "section1.xml", + "tag_type": "SECTION", + "title": "The Elements of Programming", + "parent_title": "Building Abstractions with Procedures Functions", + "depth": 1, + "paragraph_index": 3, + "content": "In this chapter we will deal only with simple procedures functions . procedures functions to manipulate compound data as well." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 3, + "content": "One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 4, + "content": "Expressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 5, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 6, + "content": "Expressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 7, + "content": "Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 8, + "content": "The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 9, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative ." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 11, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate. It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting" + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 12, + "content": "There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression." + }, + { + "source_file": "subsection1.xml", + "tag_type": "SUBSECTION", + "title": "Expressions", + "parent_title": "The Elements of Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result. This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement." + } +] \ No newline at end of file diff --git a/parser/chapter2_chunks.json b/parser/chapter2_chunks.json new file mode 100644 index 000000000..0a64816ec --- /dev/null +++ b/parser/chapter2_chunks.json @@ -0,0 +1,1352 @@ +[ + { + "source_file": "chapter2.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Data", + "parent_title": null, + "depth": 0, + "paragraph_index": 1, + "content": "We concentrated in chapter on computational processes and on the role of procedures functions in program design. We saw how to use primitive data (numbers) and primitive operations (arithmetic operations), how to combine procedures functions to form compound procedures functions through composition, conditionals, and the use of parameters, and how to abstract procedures processes by using define . function declarations. We saw that a procedure function can be regarded as a pattern for the local evolution of a process, and we classified, reasoned about, and performed simple algorithmic analyses of some common patterns for processes as embodied in procedures. functions. We also saw that higher-order procedures 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." + }, + { + "source_file": "chapter2.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Data", + "parent_title": null, + "depth": 0, + "paragraph_index": 2, + "content": "In this chapter we are going to look at more complex data. All the procedures functions in chapter operate on simple numerical data, and simple data are not sufficient for many of the problems we wish to address using computation. Programs are typically designed to model complex phenomena, and more often than not one must construct computational objects that have several parts in order to model real-world phenomena that have several aspects. Thus, whereas our focus in chapter was on building abstractions by combining procedures functions to form compound procedures, functions, we turn in this chapter to another key aspect of any programming language: the means it provides for building abstractions by combining data objects to form compound data ." + }, + { + "source_file": "chapter2.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Data", + "parent_title": null, + "depth": 0, + "paragraph_index": 3, + "content": "Why do we want compound data in a programming language? For the same reasons that we want compound procedures: functions: to elevate the conceptual level at which we can design our programs, to increase the modularity of our designs, and to enhance the expressive power of our language. Just as the ability to define procedures declare functions enables us to deal with processes at a higher conceptual level than that of the primitive operations of the language, the ability to construct compound data objects enables us to deal with data at a higher conceptual level than that of the primitive data objects of the language." + }, + { + "source_file": "chapter2.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Data", + "parent_title": null, + "depth": 0, + "paragraph_index": 4, + "content": "Consider the task of designing a system to perform add-rat add_rat that takes two rational numbers and produces their sum. In terms of simple data, a rational number can be thought of as two integers: a numerator and a denominator. Thus, we could design a program in which each rational number would be represented by two integers (a numerator and a denominator) and where add-rat add_rat would be implemented by two procedures functions (one producing the numerator of the sum and one producing the denominator). But this would be awkward, because we would then need to explicitly keep track of which numerators corresponded to which denominators. In a system intended to perform many operations on many rational numbers, such bookkeeping details would clutter the programs substantially, to say nothing of what they would do to our minds. It would be much better if we could glue together a numerator and denominator to form a pair a compound data object that our programs could manipulate in a way that would be consistent with regarding a rational number as a single conceptual unit." + }, + { + "source_file": "chapter2.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Data", + "parent_title": null, + "depth": 0, + "paragraph_index": 5, + "content": "The use of compound data also enables us to increase the modularity of our programs. If we can manipulate rational numbers directly as objects in their own right, then we can separate the part of our program that deals with rational numbers per se from the details of how rational numbers may be represented as pairs of integers. The general technique of isolating the parts of a program that deal with how data objects are represented from the parts of a program that deal with how data objects are used is a powerful design methodology called data abstraction . We will see how data abstraction makes programs much easier to design, maintain, and modify." + }, + { + "source_file": "chapter2.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Data", + "parent_title": null, + "depth": 0, + "paragraph_index": 6, + "content": "The use of compound data leads to a real increase in the expressive power of our programming language. Consider the idea of forming a linear combination $ax+by$ . We might like to write a procedure function that would accept $a$ , $b$ , $x$ , and $y$ as arguments and return the value of $ax+by$ . This presents no difficulty if the arguments are to be numbers, because we can readily define the procedure declare the function linear_combination_example (define (linear-combination a b x y) (+ (* a x) (* b y))) function linear_combination(a, b, x, y) { return a * x + b * y; } linear_combination_example linear_combination(1, 2, 3, 4); But suppose we are not concerned only with numbers. Suppose we would like to express, in procedural terms, the idea that one can form describe a process that forms linear combinations whenever addition and multiplication are defined for rational numbers, complex numbers, polynomials, or whatever. We could express this as a procedure function of the form (define (linear-combination a b x y) (add (mul a x) (mul b y))) function linear_combination(a, b, x, y) { return add(mul(a, x), mul(b, y)); } where add and mul are not the primitive procedures functions + and * but rather more complex things that will perform the appropriate operations for whatever kinds of data we pass in as the arguments a , b , x , and y . The key point is that the only thing linear-combination linear_combination should need to know about a , b , x , and y is that the procedures functions add and mul will perform the appropriate manipulations. From the perspective of the procedure function linear-combination , linear_combination , it is irrelevant what a , b , x , and y are and even more irrelevant how they might happen to be represented in terms of more primitive data. This same example shows why it is important that our programming language provide the ability to manipulate compound objects directly: Without this, there is no way for a procedure function such as linear-combination linear_combination to pass its arguments along to add and mul without having to know their detailed structure." + }, + { + "source_file": "chapter2.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Data", + "parent_title": null, + "depth": 0, + "paragraph_index": 7, + "content": "We begin this chapter by implementing the rational-number arithmetic system mentioned above. This will form the background for our discussion of compound data and data abstraction. As with compound procedures, functions, the main issue to be addressed is that of abstraction as a technique for coping with complexity, and we will see how data abstraction enables us to erect suitable abstraction barriers between different parts of a program." + }, + { + "source_file": "chapter2.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Data", + "parent_title": null, + "depth": 0, + "paragraph_index": 8, + "content": "We will see that the key to forming compound data is that a programming language should provide some kind of glue so that data objects can be combined to form more complex data objects. There are many possible kinds of glue. Indeed, we will discover how to form compound data using no special data operations at all, only procedures. functions. This will further blur the distinction between procedure function and data, which was already becoming tenuous toward the end of chapter . We will also explore some conventional techniques for representing sequences and trees. One key idea in dealing with compound data is the notion of closure that the glue we use for combining data objects should allow us to combine not only primitive data objects, but compound data objects as well. Another key idea is that compound data objects can serve as conventional interfaces for combining program modules in mix-and-match ways. We illustrate some of these ideas by presenting a simple graphics language that exploits closure." + }, + { + "source_file": "chapter2.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Data", + "parent_title": null, + "depth": 0, + "paragraph_index": 9, + "content": "We will then augment the representational power of our language by introducing symbolic expressions data whose elementary parts can be arbitrary symbols rather than only numbers. We explore various alternatives for representing sets of objects. We will find that, just as a given numerical function can be computed by many different computational processes, there are many ways in which a given data structure can be represented in terms of simpler objects, and the choice of representation can have significant impact on the time and space requirements of processes that manipulate the data. We will investigate these ideas in the context of symbolic differentiation, the representation of sets, and the encoding of information." + }, + { + "source_file": "chapter2.xml", + "tag_type": "CHAPTER", + "title": "Building Abstractions with Data", + "parent_title": null, + "depth": 0, + "paragraph_index": 10, + "content": "Next we will take up the problem of working with data that may be represented differently by different parts of a program. This leads to the need to implement generic operations , which must handle many different types of data. Maintaining modularity in the presence of generic operations requires more powerful abstraction barriers than can be erected with simple data abstraction alone. In particular, we introduce data-directed programming as a technique that allows individual data representations to be designed in isolation and then combined additively (i.e., without modification). To illustrate the power of this approach to system design, we close the chapter by applying what we have learned to the implementation of a package for performing symbolic arithmetic on polynomials, in which the coefficients of the polynomials can be integers, rational numbers, complex numbers, and even other polynomials." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 1, + "content": "As we have seen, pairs provide a primitive glue that we can use to construct compound data objects. Figure Figure shows a standard way to visualize a in this case, the pair formed by (cons 1 2) . pair(1, 2) . In this representation, which is called box-and-pointer notation , each object is shown as a pointer to a box. The box for a primitive object contains a representation of the object. For example, the box for a number contains a numeral. The box for a pair is actually a double box, the left part containing (a pointer to) the car of the pair and the right part containing the cdr . In this representation, which is called box-and-pointer notation , each compound object is shown as a pointer to a box. The box for a pair has two parts, the left part containing the head of the pair and the right part containing the tail." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 2, + "content": "We have already seen that cons pair can be used to combine not only numbers but pairs as well. (You made use of this fact, or should have, in doing exercises and .) As a consequence, pairs provide a universal building block from which we can construct all sorts of data structures. Figure Figure shows two ways to use pairs to combine the numbers 1, 2, 3, and 4. Two ways to combine 1, 2, 3, and 4 using pairs. Two ways to combine 1, 2, 3, and 4 using pairs." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 3, + "content": "The ability to create pairs whose elements are pairs is the essence of list structure s importance as a representational tool. We refer to this ability as the closure property of cons . pair . In general, an operation for combining data objects satisfies the closure property if the results of combining things with that operation can themselves be combined using the same operation. hierarchical structures structures made up of parts, which themselves are made up of parts, and so on." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 4, + "content": "From the outset of chapter , we ve made essential use of closure in dealing with procedures, functions, because all but the very simplest programs rely on the fact that the elements of a combination can themselves be combinations. In this section, we take up the consequences of closure for compound data. We describe some conventional techniques for using pairs to represent sequences and trees, and we exhibit a graphics language that illustrates closure in a vivid way." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 1, + "content": "As we have seen, pairs provide a primitive glue that we can use to construct compound data objects. Figure Figure shows a standard way to visualize a in this case, the pair formed by (cons 1 2) . pair(1, 2) . In this representation, which is called box-and-pointer notation , each object is shown as a pointer to a box. The box for a primitive object contains a representation of the object. For example, the box for a number contains a numeral. The box for a pair is actually a double box, the left part containing (a pointer to) the car of the pair and the right part containing the cdr . In this representation, which is called box-and-pointer notation , each compound object is shown as a pointer to a box. The box for a pair has two parts, the left part containing the head of the pair and the right part containing the tail." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 2, + "content": "We have already seen that cons pair can be used to combine not only numbers but pairs as well. (You made use of this fact, or should have, in doing exercises and .) As a consequence, pairs provide a universal building block from which we can construct all sorts of data structures. Figure Figure shows two ways to use pairs to combine the numbers 1, 2, 3, and 4. Two ways to combine 1, 2, 3, and 4 using pairs. Two ways to combine 1, 2, 3, and 4 using pairs." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 3, + "content": "The ability to create pairs whose elements are pairs is the essence of list structure s importance as a representational tool. We refer to this ability as the closure property of cons . pair . In general, an operation for combining data objects satisfies the closure property if the results of combining things with that operation can themselves be combined using the same operation. hierarchical structures structures made up of parts, which themselves are made up of parts, and so on." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 4, + "content": "From the outset of chapter , we ve made essential use of closure in dealing with procedures, functions, because all but the very simplest programs rely on the fact that the elements of a combination can themselves be combinations. In this section, we take up the consequences of closure for compound data. We describe some conventional techniques for using pairs to represent sequences and trees, and we exhibit a graphics language that illustrates closure in a vivid way." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 1, + "content": "As we have seen, pairs provide a primitive glue that we can use to construct compound data objects. Figure Figure shows a standard way to visualize a in this case, the pair formed by (cons 1 2) . pair(1, 2) . In this representation, which is called box-and-pointer notation , each object is shown as a pointer to a box. The box for a primitive object contains a representation of the object. For example, the box for a number contains a numeral. The box for a pair is actually a double box, the left part containing (a pointer to) the car of the pair and the right part containing the cdr . In this representation, which is called box-and-pointer notation , each compound object is shown as a pointer to a box. The box for a pair has two parts, the left part containing the head of the pair and the right part containing the tail." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 2, + "content": "We have already seen that cons pair can be used to combine not only numbers but pairs as well. (You made use of this fact, or should have, in doing exercises and .) As a consequence, pairs provide a universal building block from which we can construct all sorts of data structures. Figure Figure shows two ways to use pairs to combine the numbers 1, 2, 3, and 4. Two ways to combine 1, 2, 3, and 4 using pairs. Two ways to combine 1, 2, 3, and 4 using pairs." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 3, + "content": "The ability to create pairs whose elements are pairs is the essence of list structure s importance as a representational tool. We refer to this ability as the closure property of cons . pair . In general, an operation for combining data objects satisfies the closure property if the results of combining things with that operation can themselves be combined using the same operation. hierarchical structures structures made up of parts, which themselves are made up of parts, and so on." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 4, + "content": "From the outset of chapter , we ve made essential use of closure in dealing with procedures, functions, because all but the very simplest programs rely on the fact that the elements of a combination can themselves be combinations. In this section, we take up the consequences of closure for compound data. We describe some conventional techniques for using pairs to represent sequences and trees, and we exhibit a graphics language that illustrates closure in a vivid way." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 1, + "content": "As we have seen, pairs provide a primitive glue that we can use to construct compound data objects. Figure Figure shows a standard way to visualize a in this case, the pair formed by (cons 1 2) . pair(1, 2) . In this representation, which is called box-and-pointer notation , each object is shown as a pointer to a box. The box for a primitive object contains a representation of the object. For example, the box for a number contains a numeral. The box for a pair is actually a double box, the left part containing (a pointer to) the car of the pair and the right part containing the cdr . In this representation, which is called box-and-pointer notation , each compound object is shown as a pointer to a box. The box for a pair has two parts, the left part containing the head of the pair and the right part containing the tail." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 2, + "content": "We have already seen that cons pair can be used to combine not only numbers but pairs as well. (You made use of this fact, or should have, in doing exercises and .) As a consequence, pairs provide a universal building block from which we can construct all sorts of data structures. Figure Figure shows two ways to use pairs to combine the numbers 1, 2, 3, and 4. Two ways to combine 1, 2, 3, and 4 using pairs. Two ways to combine 1, 2, 3, and 4 using pairs." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 3, + "content": "The ability to create pairs whose elements are pairs is the essence of list structure s importance as a representational tool. We refer to this ability as the closure property of cons . pair . In general, an operation for combining data objects satisfies the closure property if the results of combining things with that operation can themselves be combined using the same operation. hierarchical structures structures made up of parts, which themselves are made up of parts, and so on." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 4, + "content": "From the outset of chapter , we ve made essential use of closure in dealing with procedures, functions, because all but the very simplest programs rely on the fact that the elements of a combination can themselves be combinations. In this section, we take up the consequences of closure for compound data. We describe some conventional techniques for using pairs to represent sequences and trees, and we exhibit a graphics language that illustrates closure in a vivid way." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 1, + "content": "As we have seen, pairs provide a primitive glue that we can use to construct compound data objects. Figure Figure shows a standard way to visualize a in this case, the pair formed by (cons 1 2) . pair(1, 2) . In this representation, which is called box-and-pointer notation , each object is shown as a pointer to a box. The box for a primitive object contains a representation of the object. For example, the box for a number contains a numeral. The box for a pair is actually a double box, the left part containing (a pointer to) the car of the pair and the right part containing the cdr . In this representation, which is called box-and-pointer notation , each compound object is shown as a pointer to a box. The box for a pair has two parts, the left part containing the head of the pair and the right part containing the tail." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 2, + "content": "We have already seen that cons pair can be used to combine not only numbers but pairs as well. (You made use of this fact, or should have, in doing exercises and .) As a consequence, pairs provide a universal building block from which we can construct all sorts of data structures. Figure Figure shows two ways to use pairs to combine the numbers 1, 2, 3, and 4. Two ways to combine 1, 2, 3, and 4 using pairs. Two ways to combine 1, 2, 3, and 4 using pairs." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 3, + "content": "The ability to create pairs whose elements are pairs is the essence of list structure s importance as a representational tool. We refer to this ability as the closure property of cons . pair . In general, an operation for combining data objects satisfies the closure property if the results of combining things with that operation can themselves be combined using the same operation. hierarchical structures structures made up of parts, which themselves are made up of parts, and so on." + }, + { + "source_file": "section2.xml", + "tag_type": "SECTION", + "title": "Hierarchical Data and the Closure Property", + "parent_title": "Building Abstractions with Data", + "depth": 1, + "paragraph_index": 4, + "content": "From the outset of chapter , we ve made essential use of closure in dealing with procedures, functions, because all but the very simplest programs rely on the fact that the elements of a combination can themselves be combinations. In this section, we take up the consequences of closure for compound data. We describe some conventional techniques for using pairs to represent sequences and trees, and we exhibit a graphics language that illustrates closure in a vivid way." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) ." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 2, + "content": "Another way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree." + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 3, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 4, + "content": "To implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves count_leaves_example 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } count_leaves_example (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 5, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree scale_tree_example 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree_example scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))" + }, + { + "source_file": "subsection2.xml", + "tag_type": "SUBSECTION", + "title": "Hierarchical Structures", + "parent_title": "Hierarchical Data and the Closure Property", + "depth": 2, + "paragraph_index": 6, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: scale_tree_example_2 (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map scale_tree_example_2 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion." + } +] \ No newline at end of file diff --git a/parser/chapter3_chunks.json b/parser/chapter3_chunks.json new file mode 100644 index 000000000..44cdb0e4a --- /dev/null +++ b/parser/chapter3_chunks.json @@ -0,0 +1,2378 @@ +[ + { + "source_file": "chapter3.xml", + "tag_type": "CHAPTER", + "title": "Modularity, Objects, and State", + "parent_title": null, + "depth": 0, + "paragraph_index": 1, + "content": "The preceding chapters introduced the basic elements from which programs are made. We saw how primitive procedures functions and primitive data are combined to construct compound entities, and we learned that abstraction is vital in helping us to cope with the complexity of large systems. But these tools are not sufficient for designing programs. Effective program synthesis also requires organizational principles that can guide us in formulating the overall design of a program. In particular, we need strategies to help us structure large systems so that they will be modular , that is, so that they can be divided naturally into coherent parts that can be separately developed and maintained." + }, + { + "source_file": "chapter3.xml", + "tag_type": "CHAPTER", + "title": "Modularity, Objects, and State", + "parent_title": null, + "depth": 0, + "paragraph_index": 2, + "content": "One powerful design strategy, which is particularly appropriate to the construction of programs for" + }, + { + "source_file": "chapter3.xml", + "tag_type": "CHAPTER", + "title": "Modularity, Objects, and State", + "parent_title": null, + "depth": 0, + "paragraph_index": 3, + "content": "To a large extent, then, the way we organize a large program is dictated by our perception of the system to be modeled. In this chapter we will investigate two prominent organizational strategies arising from two rather different world views of the structure of systems. The first organizational strategy concentrates on objects , viewing a large system as a collection of distinct objects whose behaviors may change over time. An alternative organizational strategy concentrates on the streams of information that flow in the system, much as an electrical engineer views a signal-processing system." + }, + { + "source_file": "chapter3.xml", + "tag_type": "CHAPTER", + "title": "Modularity, Objects, and State", + "parent_title": null, + "depth": 0, + "paragraph_index": 4, + "content": "Both the object-based approach and the stream-processing approach raise significant linguistic issues in programming. With objects, we must be concerned with how a computational object can change and yet maintain its identity. This will force us to abandon our old substitution model of computation (section ) in favor of a more mechanistic but less theoretically tractable environment model of computation. The difficulties of dealing with objects, change, and identity are a fundamental consequence of the need to grapple with time in our computational models. These difficulties become even greater when we allow the possibility of concurrent execution of programs. The stream approach can be most fully exploited when we decouple simulated time in our model from the order of the events that take place in the computer during evaluation. We will accomplish this using a technique known as delayed evaluation ." + }, + { + "source_file": "section3.xml", + "tag_type": "SECTION", + "title": "Modeling with Mutable Data", + "parent_title": "Modularity, Objects, and State", + "depth": 1, + "paragraph_index": 1, + "content": "Chapter dealt with compound data as a means for constructing computational objects that have several parts, in order to model real-world objects that have several aspects. In that chapter we introduced the discipline of data abstraction, according to which data structures are specified in terms of constructors, which create data objects, and selectors, which access the parts of compound data objects. But we now know that there is another aspect of data that chapter did not address. The desire to model systems composed of objects that have changing state leads us to the need to modify compound data objects, as well as to construct and select from them. In order to model compound objects with changing state, we will design data abstractions to include, in addition to selectors and constructors, operations called mutators , which modify data objects. For instance, modeling a banking system requires us to change account balances. Thus, a data structure for representing bank accounts might admit an operation (set-balance! $account$ $new$-$value$) set_balance( account , new-value ) that changes the balance of the designated account to the designated new value. Data objects for which mutators are defined are known as mutable data objects ." + }, + { + "source_file": "section3.xml", + "tag_type": "SECTION", + "title": "Modeling with Mutable Data", + "parent_title": "Modularity, Objects, and State", + "depth": 1, + "paragraph_index": 2, + "content": "Chapter introduced pairs as a general-purpose glue for synthesizing compound data. We begin this section by defining basic mutators for pairs, so that pairs can serve as building blocks for constructing mutable data objects. These mutators greatly enhance the representational power of pairs, enabling us to build data structures other than the sequences and trees that we worked with in section . We also present some examples of simulations in which complex systems are modeled as collections of objects with local state." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "section3.xml", + "tag_type": "SECTION", + "title": "Modeling with Mutable Data", + "parent_title": "Modularity, Objects, and State", + "depth": 1, + "paragraph_index": 1, + "content": "Chapter dealt with compound data as a means for constructing computational objects that have several parts, in order to model real-world objects that have several aspects. In that chapter we introduced the discipline of data abstraction, according to which data structures are specified in terms of constructors, which create data objects, and selectors, which access the parts of compound data objects. But we now know that there is another aspect of data that chapter did not address. The desire to model systems composed of objects that have changing state leads us to the need to modify compound data objects, as well as to construct and select from them. In order to model compound objects with changing state, we will design data abstractions to include, in addition to selectors and constructors, operations called mutators , which modify data objects. For instance, modeling a banking system requires us to change account balances. Thus, a data structure for representing bank accounts might admit an operation (set-balance! $account$ $new$-$value$) set_balance( account , new-value ) that changes the balance of the designated account to the designated new value. Data objects for which mutators are defined are known as mutable data objects ." + }, + { + "source_file": "section3.xml", + "tag_type": "SECTION", + "title": "Modeling with Mutable Data", + "parent_title": "Modularity, Objects, and State", + "depth": 1, + "paragraph_index": 2, + "content": "Chapter introduced pairs as a general-purpose glue for synthesizing compound data. We begin this section by defining basic mutators for pairs, so that pairs can serve as building blocks for constructing mutable data objects. These mutators greatly enhance the representational power of pairs, enabling us to build data structures other than the sequences and trees that we worked with in section . We also present some examples of simulations in which complex systems are modeled as collections of objects with local state." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "section3.xml", + "tag_type": "SECTION", + "title": "Modeling with Mutable Data", + "parent_title": "Modularity, Objects, and State", + "depth": 1, + "paragraph_index": 1, + "content": "Chapter dealt with compound data as a means for constructing computational objects that have several parts, in order to model real-world objects that have several aspects. In that chapter we introduced the discipline of data abstraction, according to which data structures are specified in terms of constructors, which create data objects, and selectors, which access the parts of compound data objects. But we now know that there is another aspect of data that chapter did not address. The desire to model systems composed of objects that have changing state leads us to the need to modify compound data objects, as well as to construct and select from them. In order to model compound objects with changing state, we will design data abstractions to include, in addition to selectors and constructors, operations called mutators , which modify data objects. For instance, modeling a banking system requires us to change account balances. Thus, a data structure for representing bank accounts might admit an operation (set-balance! $account$ $new$-$value$) set_balance( account , new-value ) that changes the balance of the designated account to the designated new value. Data objects for which mutators are defined are known as mutable data objects ." + }, + { + "source_file": "section3.xml", + "tag_type": "SECTION", + "title": "Modeling with Mutable Data", + "parent_title": "Modularity, Objects, and State", + "depth": 1, + "paragraph_index": 2, + "content": "Chapter introduced pairs as a general-purpose glue for synthesizing compound data. We begin this section by defining basic mutators for pairs, so that pairs can serve as building blocks for constructing mutable data objects. These mutators greatly enhance the representational power of pairs, enabling us to build data structures other than the sequences and trees that we worked with in section . We also present some examples of simulations in which complex systems are modeled as collections of objects with local state." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "section3.xml", + "tag_type": "SECTION", + "title": "Modeling with Mutable Data", + "parent_title": "Modularity, Objects, and State", + "depth": 1, + "paragraph_index": 1, + "content": "Chapter dealt with compound data as a means for constructing computational objects that have several parts, in order to model real-world objects that have several aspects. In that chapter we introduced the discipline of data abstraction, according to which data structures are specified in terms of constructors, which create data objects, and selectors, which access the parts of compound data objects. But we now know that there is another aspect of data that chapter did not address. The desire to model systems composed of objects that have changing state leads us to the need to modify compound data objects, as well as to construct and select from them. In order to model compound objects with changing state, we will design data abstractions to include, in addition to selectors and constructors, operations called mutators , which modify data objects. For instance, modeling a banking system requires us to change account balances. Thus, a data structure for representing bank accounts might admit an operation (set-balance! $account$ $new$-$value$) set_balance( account , new-value ) that changes the balance of the designated account to the designated new value. Data objects for which mutators are defined are known as mutable data objects ." + }, + { + "source_file": "section3.xml", + "tag_type": "SECTION", + "title": "Modeling with Mutable Data", + "parent_title": "Modularity, Objects, and State", + "depth": 1, + "paragraph_index": 2, + "content": "Chapter introduced pairs as a general-purpose glue for synthesizing compound data. We begin this section by defining basic mutators for pairs, so that pairs can serve as building blocks for constructing mutable data objects. These mutators greatly enhance the representational power of pairs, enabling us to build data structures other than the sequences and trees that we worked with in section . We also present some examples of simulations in which complex systems are modeled as collections of objects with local state." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "section3.xml", + "tag_type": "SECTION", + "title": "Modeling with Mutable Data", + "parent_title": "Modularity, Objects, and State", + "depth": 1, + "paragraph_index": 1, + "content": "Chapter dealt with compound data as a means for constructing computational objects that have several parts, in order to model real-world objects that have several aspects. In that chapter we introduced the discipline of data abstraction, according to which data structures are specified in terms of constructors, which create data objects, and selectors, which access the parts of compound data objects. But we now know that there is another aspect of data that chapter did not address. The desire to model systems composed of objects that have changing state leads us to the need to modify compound data objects, as well as to construct and select from them. In order to model compound objects with changing state, we will design data abstractions to include, in addition to selectors and constructors, operations called mutators , which modify data objects. For instance, modeling a banking system requires us to change account balances. Thus, a data structure for representing bank accounts might admit an operation (set-balance! $account$ $new$-$value$) set_balance( account , new-value ) that changes the balance of the designated account to the designated new value. Data objects for which mutators are defined are known as mutable data objects ." + }, + { + "source_file": "section3.xml", + "tag_type": "SECTION", + "title": "Modeling with Mutable Data", + "parent_title": "Modularity, Objects, and State", + "depth": 1, + "paragraph_index": 2, + "content": "Chapter introduced pairs as a general-purpose glue for synthesizing compound data. We begin this section by defining basic mutators for pairs, so that pairs can serve as building blocks for constructing mutable data objects. These mutators greatly enhance the representational power of pairs, enabling us to build data structures other than the sequences and trees that we worked with in section . We also present some examples of simulations in which complex systems are modeled as collections of objects with local state." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 2, + "content": "We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 3, + "content": "To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head . Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. lookup1_example make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 lookup1_example 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 4, + "content": "To insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value. The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 5, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 6, + "content": "In a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table." + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 7, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. lookup2_example make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc lookup2_example 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 8, + "content": "To insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 9, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }" + }, + { + "source_file": "subsection3.xml", + "tag_type": "SUBSECTION", + "title": "Representing Tables", + "parent_title": "Modeling with Mutable Data", + "depth": 2, + "paragraph_index": 10, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: operation_table_example put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 operation_table_example 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table ." + } +] \ No newline at end of file diff --git a/parser/chapter4_chunks.json b/parser/chapter4_chunks.json new file mode 100644 index 000000000..ba0429e2b --- /dev/null +++ b/parser/chapter4_chunks.json @@ -0,0 +1,8786 @@ +[ + { + "source_file": "chapter4.xml", + "tag_type": "CHAPTER", + "title": "Metalinguistic Abstraction", + "parent_title": null, + "depth": 0, + "paragraph_index": 1, + "content": "In our study of program design, we have seen that expert programmers control the complexity of their designs with the same general techniques used by designers of all complex systems. They combine primitive elements to form compound objects, they abstract compound objects to form higher-level building blocks, and they preserve modularity by adopting appropriate large-scale views of system structure. In illustrating these techniques, we have used Lisp JavaScript as a language for describing processes and for constructing computational data objects and processes to model complex phenomena in the real world. However, as we confront increasingly complex problems, we will find that Lisp, JavaScript, or indeed any fixed programming language, is not sufficient for our needs. We must constantly turn to new languages in order to express our ideas more effectively. Establishing new languages is a powerful strategy for controlling complexity in engineering design; we can often enhance our ability to deal with a complex problem by adopting a new language that enables us to describe (and hence to think about) the problem in a different way, using primitives, means of combination, and means of abstraction that are particularly well suited to the problem at hand." + }, + { + "source_file": "chapter4.xml", + "tag_type": "CHAPTER", + "title": "Metalinguistic Abstraction", + "parent_title": null, + "depth": 0, + "paragraph_index": 2, + "content": "Programming is endowed with a multitude of languages. There are physical languages, such as the procedure definition, function declaration, that are appropriate to the larger-scale organization of systems." + }, + { + "source_file": "chapter4.xml", + "tag_type": "CHAPTER", + "title": "Metalinguistic Abstraction", + "parent_title": null, + "depth": 0, + "paragraph_index": 3, + "content": "Metalinguistic abstraction establishing plays an important role in all branches of engineering design. It is particularly important to computer programming, because in programming not only can we formulate new languages but we can also implement these languages by constructing evaluators. An evaluator (or interpreter ) for a programming language is a procedure function that, when applied to a statement or expression an expression of the language, performs the actions required to evaluate that statement or expression. It is no exaggeration to regard this as the most fundamental idea in programming: The evaluator, which determines the meaning of statements and expressions in a programming language, is just another program. 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." + }, + { + "source_file": "chapter4.xml", + "tag_type": "CHAPTER", + "title": "Metalinguistic Abstraction", + "parent_title": null, + "depth": 0, + "paragraph_index": 4, + "content": "In fact, we can regard almost any program as the evaluator for some language. For instance, the polynomial manipulation system of section embodies the rules of polynomial arithmetic and implements them in terms of operations on list-structured data. If we augment this system with procedures functions to read and print polynomial expressions, we have the core of a special-purpose language for dealing with problems in symbolic mathematics. The digital-logic simulator of section and the constraint propagator of section are legitimate languages in their own right, each with its own primitives, means of combination, and means of abstraction. Seen from this perspective, the technology for coping with large-scale computer systems merges with the technology for building new computer languages, and" + }, + { + "source_file": "chapter4.xml", + "tag_type": "CHAPTER", + "title": "Metalinguistic Abstraction", + "parent_title": null, + "depth": 0, + "paragraph_index": 5, + "content": "We now embark on a tour of the technology by which languages are established in terms of other languages. In this chapter we shall use Lisp JavaScript as a base, implementing evaluators as Lisp JavaScript procedures. functions. We will take the first step in understanding how languages are implemented by building an evaluator for Lisp JavaScript itself. The language implemented by our evaluator will be a subset of the Scheme dialect of Lisp that we use in this book. JavaScript. Although the evaluator described in this chapter is written for a particular dialect of Lisp, subset of JavaScript, it contains the essential structure of an evaluator for any expression-oriented language designed for writing programs for a sequential machine. (In fact, most language processors contain, deep within them, a little Lisp evaluator.) The evaluator has been simplified for the purposes of illustration and discussion, and some features have been left out that would be important to include in a production-quality Lisp JavaScript system. Nevertheless, this simple evaluator is adequate to execute most of the programs in this book." + }, + { + "source_file": "chapter4.xml", + "tag_type": "CHAPTER", + "title": "Metalinguistic Abstraction", + "parent_title": null, + "depth": 0, + "paragraph_index": 6, + "content": "An important advantage of making the evaluator accessible as a Lisp JavaScript program is that we can implement alternative evaluation rules by describing these as modifications to the evaluator program. One place where we can use this power to good effect is to gain extra control over the ways in which computational models embody the notion of time, which was so central to the discussion in chapter . There, we mitigated some of the complexities of state and assignment by using streams to decouple the representation of time in the world from time in the computer. Our stream programs, however, were sometimes cumbersome, because they were constrained by the applicative-order evaluation of Scheme. JavaScript. In section , we ll change the underlying language to provide for a more elegant approach, by modifying the evaluator to provide for normal-order evaluation ." + }, + { + "source_file": "chapter4.xml", + "tag_type": "CHAPTER", + "title": "Metalinguistic Abstraction", + "parent_title": null, + "depth": 0, + "paragraph_index": 7, + "content": "Section implements a more ambitious linguistic change, whereby statements and expressions have many values, rather than just a single value. In this language of nondeterministic computing , it is natural to express processes that generate all possible values for statements and expressions and then search for those values that satisfy certain constraints. In terms of models of computation and time, this is like having time branch into a set of possible futures and then searching for appropriate time lines. With our nondeterministic evaluator, keeping track of multiple values and performing searches are handled automatically by the underlying mechanism of the language." + }, + { + "source_file": "chapter4.xml", + "tag_type": "CHAPTER", + "title": "Metalinguistic Abstraction", + "parent_title": null, + "depth": 0, + "paragraph_index": 8, + "content": "In section we implement a logic-programming language in which knowledge is expressed in terms of relations, rather than in terms of computations with inputs and outputs. Even though this makes the language drastically different from Lisp, JavaScript, or indeed from any conventional language, we will see that the logic-programming evaluator shares the essential structure of the Lisp JavaScript evaluator." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 1, + "content": "In chapter we stressed that computer science deals with" + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 2, + "content": "Most programming languages, including Lisp, JavaScript, are organized around computing the values of mathematical functions. Expression-oriented languages (such as Lisp, Fortran, Algol and JavaScript) (such as Lisp, C, Python, and JavaScript) capitalize on the pun that an expression that describes the value of a function may also be interpreted as a means of computing that value. Because of this, most programming languages are strongly biased toward unidirectional computations (computations with well-defined inputs and outputs). There are, however, radically different programming languages that relax this bias. We saw one such example in section , where the objects of computation were arithmetic constraints. In a constraint system the direction and the order of computation are not so well specified; in carrying out a computation the system must therefore provide more detailed how to knowledge than would be the case with an ordinary arithmetic computation. This does not mean, however, that the user is released altogether from the responsibility of providing imperative knowledge. There are many constraint networks that implement the same set of constraints, and the user must choose from the set of mathematically equivalent networks a suitable network to specify a particular computation." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 3, + "content": "The nondeterministic program evaluator of section also moves away from the view that programming is about constructing algorithms for computing unidirectional functions. In a nondeterministic language, expressions can have more than one value, and, as a result, the computation is dealing with unification ." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 4, + "content": "This approach, when it works, can be a very what is fact can be used to solve a number of different problems that would have different how to components. As an example, consider the append operation, which takes two lists as arguments and combines their elements to form a single list. In a procedural language such as Lisp, JavaScript, we could define append in terms of the basic list constructor cons , pair , as we did in section : (define (append x y) (if (null? x) y (cons (car x) (append (cdr x) y)))) function append(x, y) { return is_null(x) ? y : pair(head(x), append(tail(x), y)); } This procedure function can be regarded as a translation into Lisp JavaScript of the following two rules, the first of which covers the case where the first list is empty and the second of which handles the case of a nonempty list, which is a cons pair of two parts: For any list y , the empty list and y append to form y . For any u , v , y , and z , (cons u v) pair(u, v) and y append to form (cons u z) pair(u, z) if v and y append to form z . Using the append procedure, function, we can answer questions such as Find the append of (a b) list(\"a\", \"b\") and (c d) . list(\"c\", \"d\") . But the same two rules are also sufficient for answering the following sorts of questions, which the procedure function can t answer: Find a list y that append s with (a b) list(\"a\", \"b\") to produce (a b c d) . list(\"a\", \"b\", \"c\", \"d\") . Find all x and y that append to form (a b c d) . list(\"a\", \"b\", \"c\", \"d\") . In a append procedure function by stating the two rules about append given above. How to knowledge is provided automatically by the interpreter to allow this single pair of rules to be used to answer all three types of questions about append ." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 5, + "content": "Contemporary logic programming languages (including the one we implement here) have substantial deficiencies, in that their general how to methods can lead them into spurious infinite loops or other undesirable behavior. Logic programming is an active field of research in computer science." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 6, + "content": "Earlier in this chapter we explored the technology of implementing interpreters and described the elements that are essential to an interpreter for a Lisp-like JavaScript-like language (indeed, to an interpreter for any conventional language). Now we will apply these ideas to discuss an interpreter for a logic programming language. We call this language the query language , because it is very useful for retrieving information from data bases by formulating queries , or questions, expressed in the language. Even though the query language is very different from Lisp, JavaScript, we will find it convenient to describe the language in terms of the same general framework we have been using all along: as a collection of primitive elements, together with means of combination that enable us to combine simple elements to create more complex elements and means of abstraction that enable us to regard complex elements as single conceptual units. An interpreter for a logic programming language is considerably more complex than an interpreter for a language like Lisp. JavaScript. Nevertheless, we will see that our . In particular, there will be an evaluate part that classifies expressions according to type and an apply part that implements the language s abstraction mechanism (procedures (functions in the case of Lisp, JavaScript, and rules in the case of logic programming). Also, a central role is played in the implementation by a frame data structure, which determines the correspondence between symbols and their associated values. One additional interesting aspect of our query-language implementation is that we make substantial use of streams, which were introduced in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read query_driver_loop_example (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } query_driver_loop_example append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) process_query_example_1 parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop process_query_example_1 function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section )." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 4, + "content": "To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express append_to_form_example_5 (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 5, + "content": "The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query . evaluate_query operation_table_from_chapter_3 operation_table simple_query type append_to_form_example_5 (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 6, + "content": "The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query. simple_query stream_flatmap find_assertions apply_rules append_to_form_example_5 (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 7, + "content": "For each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 8, + "content": "And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin append_to_form_example_5 (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 9, + "content": "Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .) disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 11, + "content": "Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream append_to_form_example_5 (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null? (qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 12, + "content": "Lisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation. compound_queries_5_example compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream compound_queries_5_example (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Execute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system. execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction compound_queries_5_example (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 14, + "content": "The always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 15, + "content": "Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame. The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions append_to_form_example_5 (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 16, + "content": "Check-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream append_to_form_example_5 (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq? match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable append_to_form_example_5 (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var? pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 17, + "content": "Here is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding append_to_form_example_5 (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame. If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) . We never modify a stored binding and we never store more than one binding for a given variable." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 18, + "content": "The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 19, + "content": "If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 20, + "content": "When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 21, + "content": "Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list. For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 22, + "content": "Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules append_to_form_example_5 (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 23, + "content": "Apply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 24, + "content": "Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other. For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule append_to_form_example_5 (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 25, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 append_to_form_example_5 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 26, + "content": "The procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable append_to_form_example_5 (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 27, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 28, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable append_to_form_example_5 (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 29, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 30, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding append_to_form_example_5 (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 31, + "content": "One important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of append_to_form_example_5 (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 32, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of append_to_form_example_5 (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '? 'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 33, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules append_to_form_example_5 (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 34, + "content": "To actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule append_to_form_example_5 (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 35, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable append_to_form_example_5 (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable append_to_form_example_5 (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index append_to_form_example_5 (define (use-index? pat) (constant-symbol? (car pat)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 36, + "content": "The query system uses a few stream operations that were not presented in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 37, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed append_to_form_example_5 (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 38, + "content": "Stream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed append_to_form_example_5 (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 39, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream append_to_form_example_5 (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 40, + "content": "We saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation. Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 41, + "content": "The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 42, + "content": "Unique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 43, + "content": "The function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 append_to_form_example_5 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ? processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 44, + "content": "An exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 45, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert append_to_form_example_5 function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ . express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ? make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form_example_5 append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ . Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 46, + "content": "The function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise . A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists. In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 47, + "content": "The functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type append_to_form_example_5 functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ? tail(exp) : error(exp, \"unknown expression contents\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 48, + "content": "The following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type append_to_form_example_5 function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 49, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction append_to_form_example_5 function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 50, + "content": "The following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 append_to_form_example_5 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 51, + "content": "Type and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme append_to_form_example_5 functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 52, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type append_to_form_example_5 (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 53, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme append_to_form_example_5 (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 54, + "content": "The following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 append_to_form_example_5 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 55, + "content": "Query-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 56, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 append_to_form_example_5 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 57, + "content": "Unique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id append_to_form_example_5 (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 58, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 59, + "content": "Frames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read query_driver_loop_example (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } query_driver_loop_example append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) process_query_example_1 parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop process_query_example_1 function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section )." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 4, + "content": "To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express append_to_form_example_5 (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 5, + "content": "The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query . evaluate_query operation_table_from_chapter_3 operation_table simple_query type append_to_form_example_5 (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 6, + "content": "The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query. simple_query stream_flatmap find_assertions apply_rules append_to_form_example_5 (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 7, + "content": "For each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 8, + "content": "And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin append_to_form_example_5 (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 9, + "content": "Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .) disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 11, + "content": "Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream append_to_form_example_5 (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null? (qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 12, + "content": "Lisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation. compound_queries_5_example compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream compound_queries_5_example (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Execute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system. execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction compound_queries_5_example (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 14, + "content": "The always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 15, + "content": "Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame. The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions append_to_form_example_5 (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 16, + "content": "Check-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream append_to_form_example_5 (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq? match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable append_to_form_example_5 (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var? pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 17, + "content": "Here is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding append_to_form_example_5 (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame. If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) . We never modify a stored binding and we never store more than one binding for a given variable." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 18, + "content": "The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 19, + "content": "If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 20, + "content": "When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 21, + "content": "Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list. For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 22, + "content": "Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules append_to_form_example_5 (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 23, + "content": "Apply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 24, + "content": "Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other. For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule append_to_form_example_5 (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 25, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 append_to_form_example_5 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 26, + "content": "The procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable append_to_form_example_5 (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 27, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 28, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable append_to_form_example_5 (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 29, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 30, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding append_to_form_example_5 (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 31, + "content": "One important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of append_to_form_example_5 (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 32, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of append_to_form_example_5 (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '? 'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 33, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules append_to_form_example_5 (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 34, + "content": "To actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule append_to_form_example_5 (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 35, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable append_to_form_example_5 (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable append_to_form_example_5 (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index append_to_form_example_5 (define (use-index? pat) (constant-symbol? (car pat)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 36, + "content": "The query system uses a few stream operations that were not presented in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 37, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed append_to_form_example_5 (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 38, + "content": "Stream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed append_to_form_example_5 (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 39, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream append_to_form_example_5 (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 40, + "content": "We saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation. Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 41, + "content": "The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 42, + "content": "Unique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 43, + "content": "The function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 append_to_form_example_5 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ? processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 44, + "content": "An exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 45, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert append_to_form_example_5 function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ . express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ? make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form_example_5 append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ . Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 46, + "content": "The function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise . A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists. In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 47, + "content": "The functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type append_to_form_example_5 functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ? tail(exp) : error(exp, \"unknown expression contents\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 48, + "content": "The following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type append_to_form_example_5 function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 49, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction append_to_form_example_5 function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 50, + "content": "The following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 append_to_form_example_5 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 51, + "content": "Type and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme append_to_form_example_5 functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 52, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type append_to_form_example_5 (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 53, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme append_to_form_example_5 (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 54, + "content": "The following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 append_to_form_example_5 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 55, + "content": "Query-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 56, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 append_to_form_example_5 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 57, + "content": "Unique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id append_to_form_example_5 (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 58, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 59, + "content": "Frames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read query_driver_loop_example (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } query_driver_loop_example append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) process_query_example_1 parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop process_query_example_1 function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section )." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 4, + "content": "To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express append_to_form_example_5 (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 5, + "content": "The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query . evaluate_query operation_table_from_chapter_3 operation_table simple_query type append_to_form_example_5 (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 6, + "content": "The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query. simple_query stream_flatmap find_assertions apply_rules append_to_form_example_5 (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 7, + "content": "For each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 8, + "content": "And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin append_to_form_example_5 (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 9, + "content": "Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .) disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 11, + "content": "Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream append_to_form_example_5 (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null? (qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 12, + "content": "Lisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation. compound_queries_5_example compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream compound_queries_5_example (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Execute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system. execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction compound_queries_5_example (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 14, + "content": "The always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 15, + "content": "Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame. The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions append_to_form_example_5 (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 16, + "content": "Check-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream append_to_form_example_5 (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq? match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable append_to_form_example_5 (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var? pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 17, + "content": "Here is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding append_to_form_example_5 (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame. If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) . We never modify a stored binding and we never store more than one binding for a given variable." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 18, + "content": "The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 19, + "content": "If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 20, + "content": "When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 21, + "content": "Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list. For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 22, + "content": "Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules append_to_form_example_5 (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 23, + "content": "Apply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 24, + "content": "Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other. For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule append_to_form_example_5 (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 25, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 append_to_form_example_5 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 26, + "content": "The procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable append_to_form_example_5 (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 27, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 28, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable append_to_form_example_5 (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 29, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 30, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding append_to_form_example_5 (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 31, + "content": "One important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of append_to_form_example_5 (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 32, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of append_to_form_example_5 (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '? 'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 33, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules append_to_form_example_5 (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 34, + "content": "To actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule append_to_form_example_5 (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 35, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable append_to_form_example_5 (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable append_to_form_example_5 (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index append_to_form_example_5 (define (use-index? pat) (constant-symbol? (car pat)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 36, + "content": "The query system uses a few stream operations that were not presented in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 37, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed append_to_form_example_5 (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 38, + "content": "Stream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed append_to_form_example_5 (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 39, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream append_to_form_example_5 (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 40, + "content": "We saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation. Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 41, + "content": "The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 42, + "content": "Unique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 43, + "content": "The function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 append_to_form_example_5 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ? processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 44, + "content": "An exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 45, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert append_to_form_example_5 function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ . express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ? make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form_example_5 append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ . Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 46, + "content": "The function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise . A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists. In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 47, + "content": "The functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type append_to_form_example_5 functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ? tail(exp) : error(exp, \"unknown expression contents\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 48, + "content": "The following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type append_to_form_example_5 function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 49, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction append_to_form_example_5 function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 50, + "content": "The following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 append_to_form_example_5 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 51, + "content": "Type and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme append_to_form_example_5 functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 52, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type append_to_form_example_5 (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 53, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme append_to_form_example_5 (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 54, + "content": "The following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 append_to_form_example_5 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 55, + "content": "Query-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 56, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 append_to_form_example_5 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 57, + "content": "Unique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id append_to_form_example_5 (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 58, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 59, + "content": "Frames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read query_driver_loop_example (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } query_driver_loop_example append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) process_query_example_1 parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop process_query_example_1 function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section )." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 4, + "content": "To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express append_to_form_example_5 (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 5, + "content": "The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query . evaluate_query operation_table_from_chapter_3 operation_table simple_query type append_to_form_example_5 (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 6, + "content": "The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query. simple_query stream_flatmap find_assertions apply_rules append_to_form_example_5 (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 7, + "content": "For each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 8, + "content": "And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin append_to_form_example_5 (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 9, + "content": "Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .) disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 11, + "content": "Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream append_to_form_example_5 (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null? (qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 12, + "content": "Lisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation. compound_queries_5_example compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream compound_queries_5_example (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Execute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system. execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction compound_queries_5_example (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 14, + "content": "The always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 15, + "content": "Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame. The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions append_to_form_example_5 (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 16, + "content": "Check-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream append_to_form_example_5 (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq? match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable append_to_form_example_5 (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var? pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 17, + "content": "Here is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding append_to_form_example_5 (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame. If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) . We never modify a stored binding and we never store more than one binding for a given variable." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 18, + "content": "The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 19, + "content": "If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 20, + "content": "When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 21, + "content": "Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list. For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 22, + "content": "Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules append_to_form_example_5 (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 23, + "content": "Apply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 24, + "content": "Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other. For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule append_to_form_example_5 (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 25, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 append_to_form_example_5 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 26, + "content": "The procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable append_to_form_example_5 (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 27, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 28, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable append_to_form_example_5 (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 29, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 30, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding append_to_form_example_5 (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 31, + "content": "One important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of append_to_form_example_5 (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 32, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of append_to_form_example_5 (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '? 'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 33, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules append_to_form_example_5 (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 34, + "content": "To actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule append_to_form_example_5 (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 35, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable append_to_form_example_5 (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable append_to_form_example_5 (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index append_to_form_example_5 (define (use-index? pat) (constant-symbol? (car pat)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 36, + "content": "The query system uses a few stream operations that were not presented in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 37, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed append_to_form_example_5 (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 38, + "content": "Stream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed append_to_form_example_5 (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 39, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream append_to_form_example_5 (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 40, + "content": "We saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation. Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 41, + "content": "The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 42, + "content": "Unique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 43, + "content": "The function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 append_to_form_example_5 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ? processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 44, + "content": "An exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 45, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert append_to_form_example_5 function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ . express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ? make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form_example_5 append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ . Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 46, + "content": "The function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise . A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists. In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 47, + "content": "The functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type append_to_form_example_5 functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ? tail(exp) : error(exp, \"unknown expression contents\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 48, + "content": "The following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type append_to_form_example_5 function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 49, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction append_to_form_example_5 function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 50, + "content": "The following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 append_to_form_example_5 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 51, + "content": "Type and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme append_to_form_example_5 functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 52, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type append_to_form_example_5 (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 53, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme append_to_form_example_5 (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 54, + "content": "The following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 append_to_form_example_5 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 55, + "content": "Query-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 56, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 append_to_form_example_5 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 57, + "content": "Unique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id append_to_form_example_5 (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 58, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 59, + "content": "Frames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }" + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 1, + "content": "In chapter we stressed that computer science deals with" + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 2, + "content": "Most programming languages, including Lisp, JavaScript, are organized around computing the values of mathematical functions. Expression-oriented languages (such as Lisp, Fortran, Algol and JavaScript) (such as Lisp, C, Python, and JavaScript) capitalize on the pun that an expression that describes the value of a function may also be interpreted as a means of computing that value. Because of this, most programming languages are strongly biased toward unidirectional computations (computations with well-defined inputs and outputs). There are, however, radically different programming languages that relax this bias. We saw one such example in section , where the objects of computation were arithmetic constraints. In a constraint system the direction and the order of computation are not so well specified; in carrying out a computation the system must therefore provide more detailed how to knowledge than would be the case with an ordinary arithmetic computation. This does not mean, however, that the user is released altogether from the responsibility of providing imperative knowledge. There are many constraint networks that implement the same set of constraints, and the user must choose from the set of mathematically equivalent networks a suitable network to specify a particular computation." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 3, + "content": "The nondeterministic program evaluator of section also moves away from the view that programming is about constructing algorithms for computing unidirectional functions. In a nondeterministic language, expressions can have more than one value, and, as a result, the computation is dealing with unification ." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 4, + "content": "This approach, when it works, can be a very what is fact can be used to solve a number of different problems that would have different how to components. As an example, consider the append operation, which takes two lists as arguments and combines their elements to form a single list. In a procedural language such as Lisp, JavaScript, we could define append in terms of the basic list constructor cons , pair , as we did in section : (define (append x y) (if (null? x) y (cons (car x) (append (cdr x) y)))) function append(x, y) { return is_null(x) ? y : pair(head(x), append(tail(x), y)); } This procedure function can be regarded as a translation into Lisp JavaScript of the following two rules, the first of which covers the case where the first list is empty and the second of which handles the case of a nonempty list, which is a cons pair of two parts: For any list y , the empty list and y append to form y . For any u , v , y , and z , (cons u v) pair(u, v) and y append to form (cons u z) pair(u, z) if v and y append to form z . Using the append procedure, function, we can answer questions such as Find the append of (a b) list(\"a\", \"b\") and (c d) . list(\"c\", \"d\") . But the same two rules are also sufficient for answering the following sorts of questions, which the procedure function can t answer: Find a list y that append s with (a b) list(\"a\", \"b\") to produce (a b c d) . list(\"a\", \"b\", \"c\", \"d\") . Find all x and y that append to form (a b c d) . list(\"a\", \"b\", \"c\", \"d\") . In a append procedure function by stating the two rules about append given above. How to knowledge is provided automatically by the interpreter to allow this single pair of rules to be used to answer all three types of questions about append ." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 5, + "content": "Contemporary logic programming languages (including the one we implement here) have substantial deficiencies, in that their general how to methods can lead them into spurious infinite loops or other undesirable behavior. Logic programming is an active field of research in computer science." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 6, + "content": "Earlier in this chapter we explored the technology of implementing interpreters and described the elements that are essential to an interpreter for a Lisp-like JavaScript-like language (indeed, to an interpreter for any conventional language). Now we will apply these ideas to discuss an interpreter for a logic programming language. We call this language the query language , because it is very useful for retrieving information from data bases by formulating queries , or questions, expressed in the language. Even though the query language is very different from Lisp, JavaScript, we will find it convenient to describe the language in terms of the same general framework we have been using all along: as a collection of primitive elements, together with means of combination that enable us to combine simple elements to create more complex elements and means of abstraction that enable us to regard complex elements as single conceptual units. An interpreter for a logic programming language is considerably more complex than an interpreter for a language like Lisp. JavaScript. Nevertheless, we will see that our . In particular, there will be an evaluate part that classifies expressions according to type and an apply part that implements the language s abstraction mechanism (procedures (functions in the case of Lisp, JavaScript, and rules in the case of logic programming). Also, a central role is played in the implementation by a frame data structure, which determines the correspondence between symbols and their associated values. One additional interesting aspect of our query-language implementation is that we make substantial use of streams, which were introduced in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read query_driver_loop_example (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } query_driver_loop_example append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) process_query_example_1 parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop process_query_example_1 function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section )." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 4, + "content": "To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express append_to_form_example_5 (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 5, + "content": "The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query . evaluate_query operation_table_from_chapter_3 operation_table simple_query type append_to_form_example_5 (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 6, + "content": "The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query. simple_query stream_flatmap find_assertions apply_rules append_to_form_example_5 (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 7, + "content": "For each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 8, + "content": "And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin append_to_form_example_5 (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 9, + "content": "Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .) disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 11, + "content": "Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream append_to_form_example_5 (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null? (qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 12, + "content": "Lisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation. compound_queries_5_example compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream compound_queries_5_example (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Execute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system. execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction compound_queries_5_example (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 14, + "content": "The always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 15, + "content": "Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame. The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions append_to_form_example_5 (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 16, + "content": "Check-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream append_to_form_example_5 (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq? match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable append_to_form_example_5 (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var? pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 17, + "content": "Here is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding append_to_form_example_5 (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame. If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) . We never modify a stored binding and we never store more than one binding for a given variable." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 18, + "content": "The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 19, + "content": "If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 20, + "content": "When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 21, + "content": "Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list. For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 22, + "content": "Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules append_to_form_example_5 (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 23, + "content": "Apply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 24, + "content": "Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other. For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule append_to_form_example_5 (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 25, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 append_to_form_example_5 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 26, + "content": "The procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable append_to_form_example_5 (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 27, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 28, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable append_to_form_example_5 (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 29, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 30, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding append_to_form_example_5 (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 31, + "content": "One important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of append_to_form_example_5 (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 32, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of append_to_form_example_5 (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '? 'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 33, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules append_to_form_example_5 (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 34, + "content": "To actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule append_to_form_example_5 (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 35, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable append_to_form_example_5 (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable append_to_form_example_5 (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index append_to_form_example_5 (define (use-index? pat) (constant-symbol? (car pat)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 36, + "content": "The query system uses a few stream operations that were not presented in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 37, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed append_to_form_example_5 (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 38, + "content": "Stream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed append_to_form_example_5 (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 39, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream append_to_form_example_5 (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 40, + "content": "We saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation. Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 41, + "content": "The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 42, + "content": "Unique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 43, + "content": "The function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 append_to_form_example_5 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ? processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 44, + "content": "An exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 45, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert append_to_form_example_5 function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ . express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ? make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form_example_5 append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ . Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 46, + "content": "The function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise . A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists. In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 47, + "content": "The functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type append_to_form_example_5 functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ? tail(exp) : error(exp, \"unknown expression contents\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 48, + "content": "The following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type append_to_form_example_5 function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 49, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction append_to_form_example_5 function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 50, + "content": "The following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 append_to_form_example_5 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 51, + "content": "Type and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme append_to_form_example_5 functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 52, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type append_to_form_example_5 (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 53, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme append_to_form_example_5 (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 54, + "content": "The following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 append_to_form_example_5 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 55, + "content": "Query-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 56, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 append_to_form_example_5 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 57, + "content": "Unique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id append_to_form_example_5 (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 58, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 59, + "content": "Frames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read query_driver_loop_example (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } query_driver_loop_example append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) process_query_example_1 parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop process_query_example_1 function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section )." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 4, + "content": "To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express append_to_form_example_5 (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 5, + "content": "The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query . evaluate_query operation_table_from_chapter_3 operation_table simple_query type append_to_form_example_5 (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 6, + "content": "The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query. simple_query stream_flatmap find_assertions apply_rules append_to_form_example_5 (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 7, + "content": "For each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 8, + "content": "And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin append_to_form_example_5 (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 9, + "content": "Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .) disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 11, + "content": "Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream append_to_form_example_5 (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null? (qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 12, + "content": "Lisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation. compound_queries_5_example compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream compound_queries_5_example (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Execute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system. execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction compound_queries_5_example (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 14, + "content": "The always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 15, + "content": "Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame. The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions append_to_form_example_5 (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 16, + "content": "Check-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream append_to_form_example_5 (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq? match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable append_to_form_example_5 (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var? pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 17, + "content": "Here is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding append_to_form_example_5 (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame. If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) . We never modify a stored binding and we never store more than one binding for a given variable." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 18, + "content": "The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 19, + "content": "If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 20, + "content": "When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 21, + "content": "Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list. For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 22, + "content": "Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules append_to_form_example_5 (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 23, + "content": "Apply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 24, + "content": "Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other. For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule append_to_form_example_5 (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 25, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 append_to_form_example_5 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 26, + "content": "The procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable append_to_form_example_5 (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 27, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 28, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable append_to_form_example_5 (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 29, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 30, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding append_to_form_example_5 (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 31, + "content": "One important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of append_to_form_example_5 (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 32, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of append_to_form_example_5 (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '? 'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 33, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules append_to_form_example_5 (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 34, + "content": "To actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule append_to_form_example_5 (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 35, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable append_to_form_example_5 (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable append_to_form_example_5 (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index append_to_form_example_5 (define (use-index? pat) (constant-symbol? (car pat)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 36, + "content": "The query system uses a few stream operations that were not presented in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 37, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed append_to_form_example_5 (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 38, + "content": "Stream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed append_to_form_example_5 (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 39, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream append_to_form_example_5 (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 40, + "content": "We saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation. Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 41, + "content": "The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 42, + "content": "Unique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 43, + "content": "The function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 append_to_form_example_5 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ? processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 44, + "content": "An exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 45, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert append_to_form_example_5 function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ . express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ? make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form_example_5 append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ . Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 46, + "content": "The function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise . A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists. In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 47, + "content": "The functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type append_to_form_example_5 functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ? tail(exp) : error(exp, \"unknown expression contents\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 48, + "content": "The following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type append_to_form_example_5 function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 49, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction append_to_form_example_5 function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 50, + "content": "The following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 append_to_form_example_5 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 51, + "content": "Type and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme append_to_form_example_5 functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 52, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type append_to_form_example_5 (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 53, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme append_to_form_example_5 (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 54, + "content": "The following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 append_to_form_example_5 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 55, + "content": "Query-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 56, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 append_to_form_example_5 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 57, + "content": "Unique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id append_to_form_example_5 (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 58, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 59, + "content": "Frames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read query_driver_loop_example (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } query_driver_loop_example append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) process_query_example_1 parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop process_query_example_1 function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section )." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 4, + "content": "To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express append_to_form_example_5 (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 5, + "content": "The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query . evaluate_query operation_table_from_chapter_3 operation_table simple_query type append_to_form_example_5 (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 6, + "content": "The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query. simple_query stream_flatmap find_assertions apply_rules append_to_form_example_5 (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 7, + "content": "For each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 8, + "content": "And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin append_to_form_example_5 (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 9, + "content": "Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .) disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 11, + "content": "Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream append_to_form_example_5 (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null? (qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 12, + "content": "Lisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation. compound_queries_5_example compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream compound_queries_5_example (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Execute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system. execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction compound_queries_5_example (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 14, + "content": "The always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 15, + "content": "Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame. The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions append_to_form_example_5 (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 16, + "content": "Check-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream append_to_form_example_5 (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq? match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable append_to_form_example_5 (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var? pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 17, + "content": "Here is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding append_to_form_example_5 (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame. If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) . We never modify a stored binding and we never store more than one binding for a given variable." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 18, + "content": "The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 19, + "content": "If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 20, + "content": "When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 21, + "content": "Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list. For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 22, + "content": "Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules append_to_form_example_5 (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 23, + "content": "Apply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 24, + "content": "Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other. For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule append_to_form_example_5 (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 25, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 append_to_form_example_5 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 26, + "content": "The procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable append_to_form_example_5 (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 27, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 28, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable append_to_form_example_5 (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 29, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 30, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding append_to_form_example_5 (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 31, + "content": "One important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of append_to_form_example_5 (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 32, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of append_to_form_example_5 (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '? 'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 33, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules append_to_form_example_5 (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 34, + "content": "To actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule append_to_form_example_5 (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 35, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable append_to_form_example_5 (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable append_to_form_example_5 (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index append_to_form_example_5 (define (use-index? pat) (constant-symbol? (car pat)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 36, + "content": "The query system uses a few stream operations that were not presented in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 37, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed append_to_form_example_5 (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 38, + "content": "Stream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed append_to_form_example_5 (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 39, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream append_to_form_example_5 (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 40, + "content": "We saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation. Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 41, + "content": "The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 42, + "content": "Unique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 43, + "content": "The function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 append_to_form_example_5 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ? processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 44, + "content": "An exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 45, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert append_to_form_example_5 function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ . express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ? make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form_example_5 append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ . Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 46, + "content": "The function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise . A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists. In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 47, + "content": "The functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type append_to_form_example_5 functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ? tail(exp) : error(exp, \"unknown expression contents\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 48, + "content": "The following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type append_to_form_example_5 function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 49, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction append_to_form_example_5 function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 50, + "content": "The following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 append_to_form_example_5 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 51, + "content": "Type and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme append_to_form_example_5 functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 52, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type append_to_form_example_5 (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 53, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme append_to_form_example_5 (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 54, + "content": "The following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 append_to_form_example_5 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 55, + "content": "Query-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 56, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 append_to_form_example_5 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 57, + "content": "Unique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id append_to_form_example_5 (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 58, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 59, + "content": "Frames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read query_driver_loop_example (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } query_driver_loop_example append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) process_query_example_1 parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop process_query_example_1 function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section )." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 4, + "content": "To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express append_to_form_example_5 (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 5, + "content": "The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query . evaluate_query operation_table_from_chapter_3 operation_table simple_query type append_to_form_example_5 (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 6, + "content": "The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query. simple_query stream_flatmap find_assertions apply_rules append_to_form_example_5 (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 7, + "content": "For each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 8, + "content": "And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin append_to_form_example_5 (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 9, + "content": "Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .) disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 11, + "content": "Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream append_to_form_example_5 (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null? (qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 12, + "content": "Lisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation. compound_queries_5_example compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream compound_queries_5_example (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Execute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system. execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction compound_queries_5_example (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 14, + "content": "The always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 15, + "content": "Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame. The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions append_to_form_example_5 (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 16, + "content": "Check-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream append_to_form_example_5 (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq? match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable append_to_form_example_5 (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var? pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 17, + "content": "Here is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding append_to_form_example_5 (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame. If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) . We never modify a stored binding and we never store more than one binding for a given variable." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 18, + "content": "The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 19, + "content": "If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 20, + "content": "When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 21, + "content": "Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list. For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 22, + "content": "Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules append_to_form_example_5 (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 23, + "content": "Apply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 24, + "content": "Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other. For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule append_to_form_example_5 (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 25, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 append_to_form_example_5 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 26, + "content": "The procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable append_to_form_example_5 (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 27, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 28, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable append_to_form_example_5 (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 29, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 30, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding append_to_form_example_5 (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 31, + "content": "One important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of append_to_form_example_5 (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 32, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of append_to_form_example_5 (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '? 'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 33, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules append_to_form_example_5 (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 34, + "content": "To actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule append_to_form_example_5 (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 35, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable append_to_form_example_5 (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable append_to_form_example_5 (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index append_to_form_example_5 (define (use-index? pat) (constant-symbol? (car pat)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 36, + "content": "The query system uses a few stream operations that were not presented in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 37, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed append_to_form_example_5 (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 38, + "content": "Stream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed append_to_form_example_5 (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 39, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream append_to_form_example_5 (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 40, + "content": "We saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation. Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 41, + "content": "The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 42, + "content": "Unique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 43, + "content": "The function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 append_to_form_example_5 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ? processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 44, + "content": "An exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 45, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert append_to_form_example_5 function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ . express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ? make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form_example_5 append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ . Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 46, + "content": "The function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise . A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists. In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 47, + "content": "The functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type append_to_form_example_5 functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ? tail(exp) : error(exp, \"unknown expression contents\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 48, + "content": "The following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type append_to_form_example_5 function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 49, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction append_to_form_example_5 function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 50, + "content": "The following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 append_to_form_example_5 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 51, + "content": "Type and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme append_to_form_example_5 functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 52, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type append_to_form_example_5 (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 53, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme append_to_form_example_5 (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 54, + "content": "The following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 append_to_form_example_5 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 55, + "content": "Query-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 56, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 append_to_form_example_5 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 57, + "content": "Unique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id append_to_form_example_5 (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 58, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 59, + "content": "Frames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }" + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 1, + "content": "In chapter we stressed that computer science deals with" + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 2, + "content": "Most programming languages, including Lisp, JavaScript, are organized around computing the values of mathematical functions. Expression-oriented languages (such as Lisp, Fortran, Algol and JavaScript) (such as Lisp, C, Python, and JavaScript) capitalize on the pun that an expression that describes the value of a function may also be interpreted as a means of computing that value. Because of this, most programming languages are strongly biased toward unidirectional computations (computations with well-defined inputs and outputs). There are, however, radically different programming languages that relax this bias. We saw one such example in section , where the objects of computation were arithmetic constraints. In a constraint system the direction and the order of computation are not so well specified; in carrying out a computation the system must therefore provide more detailed how to knowledge than would be the case with an ordinary arithmetic computation. This does not mean, however, that the user is released altogether from the responsibility of providing imperative knowledge. There are many constraint networks that implement the same set of constraints, and the user must choose from the set of mathematically equivalent networks a suitable network to specify a particular computation." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 3, + "content": "The nondeterministic program evaluator of section also moves away from the view that programming is about constructing algorithms for computing unidirectional functions. In a nondeterministic language, expressions can have more than one value, and, as a result, the computation is dealing with unification ." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 4, + "content": "This approach, when it works, can be a very what is fact can be used to solve a number of different problems that would have different how to components. As an example, consider the append operation, which takes two lists as arguments and combines their elements to form a single list. In a procedural language such as Lisp, JavaScript, we could define append in terms of the basic list constructor cons , pair , as we did in section : (define (append x y) (if (null? x) y (cons (car x) (append (cdr x) y)))) function append(x, y) { return is_null(x) ? y : pair(head(x), append(tail(x), y)); } This procedure function can be regarded as a translation into Lisp JavaScript of the following two rules, the first of which covers the case where the first list is empty and the second of which handles the case of a nonempty list, which is a cons pair of two parts: For any list y , the empty list and y append to form y . For any u , v , y , and z , (cons u v) pair(u, v) and y append to form (cons u z) pair(u, z) if v and y append to form z . Using the append procedure, function, we can answer questions such as Find the append of (a b) list(\"a\", \"b\") and (c d) . list(\"c\", \"d\") . But the same two rules are also sufficient for answering the following sorts of questions, which the procedure function can t answer: Find a list y that append s with (a b) list(\"a\", \"b\") to produce (a b c d) . list(\"a\", \"b\", \"c\", \"d\") . Find all x and y that append to form (a b c d) . list(\"a\", \"b\", \"c\", \"d\") . In a append procedure function by stating the two rules about append given above. How to knowledge is provided automatically by the interpreter to allow this single pair of rules to be used to answer all three types of questions about append ." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 5, + "content": "Contemporary logic programming languages (including the one we implement here) have substantial deficiencies, in that their general how to methods can lead them into spurious infinite loops or other undesirable behavior. Logic programming is an active field of research in computer science." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 6, + "content": "Earlier in this chapter we explored the technology of implementing interpreters and described the elements that are essential to an interpreter for a Lisp-like JavaScript-like language (indeed, to an interpreter for any conventional language). Now we will apply these ideas to discuss an interpreter for a logic programming language. We call this language the query language , because it is very useful for retrieving information from data bases by formulating queries , or questions, expressed in the language. Even though the query language is very different from Lisp, JavaScript, we will find it convenient to describe the language in terms of the same general framework we have been using all along: as a collection of primitive elements, together with means of combination that enable us to combine simple elements to create more complex elements and means of abstraction that enable us to regard complex elements as single conceptual units. An interpreter for a logic programming language is considerably more complex than an interpreter for a language like Lisp. JavaScript. Nevertheless, we will see that our . In particular, there will be an evaluate part that classifies expressions according to type and an apply part that implements the language s abstraction mechanism (procedures (functions in the case of Lisp, JavaScript, and rules in the case of logic programming). Also, a central role is played in the implementation by a frame data structure, which determines the correspondence between symbols and their associated values. One additional interesting aspect of our query-language implementation is that we make substantial use of streams, which were introduced in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read query_driver_loop_example (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } query_driver_loop_example append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) process_query_example_1 parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop process_query_example_1 function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section )." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 4, + "content": "To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express append_to_form_example_5 (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 5, + "content": "The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query . evaluate_query operation_table_from_chapter_3 operation_table simple_query type append_to_form_example_5 (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 6, + "content": "The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query. simple_query stream_flatmap find_assertions apply_rules append_to_form_example_5 (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 7, + "content": "For each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 8, + "content": "And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin append_to_form_example_5 (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 9, + "content": "Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .) disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 11, + "content": "Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream append_to_form_example_5 (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null? (qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 12, + "content": "Lisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation. compound_queries_5_example compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream compound_queries_5_example (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Execute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system. execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction compound_queries_5_example (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 14, + "content": "The always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 15, + "content": "Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame. The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions append_to_form_example_5 (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 16, + "content": "Check-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream append_to_form_example_5 (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq? match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable append_to_form_example_5 (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var? pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 17, + "content": "Here is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding append_to_form_example_5 (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame. If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) . We never modify a stored binding and we never store more than one binding for a given variable." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 18, + "content": "The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 19, + "content": "If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 20, + "content": "When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 21, + "content": "Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list. For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 22, + "content": "Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules append_to_form_example_5 (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 23, + "content": "Apply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 24, + "content": "Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other. For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule append_to_form_example_5 (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 25, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 append_to_form_example_5 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 26, + "content": "The procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable append_to_form_example_5 (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 27, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 28, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable append_to_form_example_5 (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 29, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 30, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding append_to_form_example_5 (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 31, + "content": "One important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of append_to_form_example_5 (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 32, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of append_to_form_example_5 (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '? 'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 33, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules append_to_form_example_5 (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 34, + "content": "To actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule append_to_form_example_5 (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 35, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable append_to_form_example_5 (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable append_to_form_example_5 (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index append_to_form_example_5 (define (use-index? pat) (constant-symbol? (car pat)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 36, + "content": "The query system uses a few stream operations that were not presented in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 37, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed append_to_form_example_5 (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 38, + "content": "Stream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed append_to_form_example_5 (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 39, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream append_to_form_example_5 (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 40, + "content": "We saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation. Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 41, + "content": "The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 42, + "content": "Unique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 43, + "content": "The function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 append_to_form_example_5 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ? processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 44, + "content": "An exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 45, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert append_to_form_example_5 function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ . express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ? make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form_example_5 append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ . Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 46, + "content": "The function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise . A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists. In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 47, + "content": "The functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type append_to_form_example_5 functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ? tail(exp) : error(exp, \"unknown expression contents\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 48, + "content": "The following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type append_to_form_example_5 function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 49, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction append_to_form_example_5 function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 50, + "content": "The following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 append_to_form_example_5 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 51, + "content": "Type and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme append_to_form_example_5 functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 52, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type append_to_form_example_5 (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 53, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme append_to_form_example_5 (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 54, + "content": "The following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 append_to_form_example_5 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 55, + "content": "Query-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 56, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 append_to_form_example_5 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 57, + "content": "Unique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id append_to_form_example_5 (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 58, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 59, + "content": "Frames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read query_driver_loop_example (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } query_driver_loop_example append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) process_query_example_1 parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop process_query_example_1 function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section )." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 4, + "content": "To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express append_to_form_example_5 (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 5, + "content": "The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query . evaluate_query operation_table_from_chapter_3 operation_table simple_query type append_to_form_example_5 (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 6, + "content": "The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query. simple_query stream_flatmap find_assertions apply_rules append_to_form_example_5 (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 7, + "content": "For each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 8, + "content": "And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin append_to_form_example_5 (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 9, + "content": "Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .) disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 11, + "content": "Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream append_to_form_example_5 (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null? (qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 12, + "content": "Lisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation. compound_queries_5_example compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream compound_queries_5_example (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Execute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system. execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction compound_queries_5_example (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 14, + "content": "The always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 15, + "content": "Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame. The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions append_to_form_example_5 (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 16, + "content": "Check-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream append_to_form_example_5 (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq? match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable append_to_form_example_5 (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var? pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 17, + "content": "Here is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding append_to_form_example_5 (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame. If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) . We never modify a stored binding and we never store more than one binding for a given variable." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 18, + "content": "The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 19, + "content": "If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 20, + "content": "When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 21, + "content": "Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list. For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 22, + "content": "Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules append_to_form_example_5 (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 23, + "content": "Apply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 24, + "content": "Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other. For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule append_to_form_example_5 (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 25, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 append_to_form_example_5 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 26, + "content": "The procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable append_to_form_example_5 (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 27, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 28, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable append_to_form_example_5 (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 29, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 30, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding append_to_form_example_5 (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 31, + "content": "One important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of append_to_form_example_5 (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 32, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of append_to_form_example_5 (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '? 'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 33, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules append_to_form_example_5 (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 34, + "content": "To actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule append_to_form_example_5 (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 35, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable append_to_form_example_5 (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable append_to_form_example_5 (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index append_to_form_example_5 (define (use-index? pat) (constant-symbol? (car pat)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 36, + "content": "The query system uses a few stream operations that were not presented in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 37, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed append_to_form_example_5 (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 38, + "content": "Stream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed append_to_form_example_5 (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 39, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream append_to_form_example_5 (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 40, + "content": "We saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation. Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 41, + "content": "The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 42, + "content": "Unique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 43, + "content": "The function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 append_to_form_example_5 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ? processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 44, + "content": "An exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 45, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert append_to_form_example_5 function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ . express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ? make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form_example_5 append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ . Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 46, + "content": "The function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise . A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists. In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 47, + "content": "The functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type append_to_form_example_5 functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ? tail(exp) : error(exp, \"unknown expression contents\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 48, + "content": "The following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type append_to_form_example_5 function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 49, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction append_to_form_example_5 function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 50, + "content": "The following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 append_to_form_example_5 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 51, + "content": "Type and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme append_to_form_example_5 functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 52, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type append_to_form_example_5 (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 53, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme append_to_form_example_5 (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 54, + "content": "The following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 append_to_form_example_5 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 55, + "content": "Query-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 56, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 append_to_form_example_5 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 57, + "content": "Unique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id append_to_form_example_5 (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 58, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 59, + "content": "Frames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read query_driver_loop_example (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } query_driver_loop_example append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) process_query_example_1 parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop process_query_example_1 function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section )." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 4, + "content": "To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express append_to_form_example_5 (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 5, + "content": "The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query . evaluate_query operation_table_from_chapter_3 operation_table simple_query type append_to_form_example_5 (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 6, + "content": "The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query. simple_query stream_flatmap find_assertions apply_rules append_to_form_example_5 (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 7, + "content": "For each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 8, + "content": "And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin append_to_form_example_5 (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 9, + "content": "Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .) disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 11, + "content": "Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream append_to_form_example_5 (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null? (qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 12, + "content": "Lisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation. compound_queries_5_example compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream compound_queries_5_example (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Execute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system. execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction compound_queries_5_example (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 14, + "content": "The always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 15, + "content": "Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame. The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions append_to_form_example_5 (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 16, + "content": "Check-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream append_to_form_example_5 (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq? match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable append_to_form_example_5 (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var? pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 17, + "content": "Here is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding append_to_form_example_5 (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame. If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) . We never modify a stored binding and we never store more than one binding for a given variable." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 18, + "content": "The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 19, + "content": "If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 20, + "content": "When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 21, + "content": "Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list. For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 22, + "content": "Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules append_to_form_example_5 (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 23, + "content": "Apply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 24, + "content": "Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other. For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule append_to_form_example_5 (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 25, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 append_to_form_example_5 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 26, + "content": "The procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable append_to_form_example_5 (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 27, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 28, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable append_to_form_example_5 (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 29, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 30, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding append_to_form_example_5 (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 31, + "content": "One important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of append_to_form_example_5 (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 32, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of append_to_form_example_5 (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '? 'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 33, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules append_to_form_example_5 (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 34, + "content": "To actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule append_to_form_example_5 (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 35, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable append_to_form_example_5 (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable append_to_form_example_5 (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index append_to_form_example_5 (define (use-index? pat) (constant-symbol? (car pat)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 36, + "content": "The query system uses a few stream operations that were not presented in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 37, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed append_to_form_example_5 (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 38, + "content": "Stream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed append_to_form_example_5 (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 39, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream append_to_form_example_5 (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 40, + "content": "We saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation. Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 41, + "content": "The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 42, + "content": "Unique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 43, + "content": "The function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 append_to_form_example_5 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ? processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 44, + "content": "An exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 45, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert append_to_form_example_5 function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ . express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ? make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form_example_5 append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ . Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 46, + "content": "The function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise . A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists. In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 47, + "content": "The functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type append_to_form_example_5 functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ? tail(exp) : error(exp, \"unknown expression contents\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 48, + "content": "The following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type append_to_form_example_5 function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 49, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction append_to_form_example_5 function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 50, + "content": "The following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 append_to_form_example_5 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 51, + "content": "Type and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme append_to_form_example_5 functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 52, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type append_to_form_example_5 (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 53, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme append_to_form_example_5 (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 54, + "content": "The following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 append_to_form_example_5 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 55, + "content": "Query-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 56, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 append_to_form_example_5 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 57, + "content": "Unique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id append_to_form_example_5 (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 58, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 59, + "content": "Frames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read query_driver_loop_example (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } query_driver_loop_example append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) process_query_example_1 parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop process_query_example_1 function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section )." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 4, + "content": "To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express append_to_form_example_5 (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 5, + "content": "The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query . evaluate_query operation_table_from_chapter_3 operation_table simple_query type append_to_form_example_5 (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 6, + "content": "The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query. simple_query stream_flatmap find_assertions apply_rules append_to_form_example_5 (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 7, + "content": "For each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 8, + "content": "And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin append_to_form_example_5 (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 9, + "content": "Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .) disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 11, + "content": "Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream append_to_form_example_5 (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null? (qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 12, + "content": "Lisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation. compound_queries_5_example compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream compound_queries_5_example (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Execute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system. execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction compound_queries_5_example (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 14, + "content": "The always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 15, + "content": "Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame. The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions append_to_form_example_5 (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 16, + "content": "Check-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream append_to_form_example_5 (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq? match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable append_to_form_example_5 (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var? pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 17, + "content": "Here is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding append_to_form_example_5 (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame. If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) . We never modify a stored binding and we never store more than one binding for a given variable." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 18, + "content": "The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 19, + "content": "If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 20, + "content": "When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 21, + "content": "Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list. For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 22, + "content": "Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules append_to_form_example_5 (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 23, + "content": "Apply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 24, + "content": "Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other. For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule append_to_form_example_5 (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 25, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 append_to_form_example_5 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 26, + "content": "The procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable append_to_form_example_5 (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 27, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 28, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable append_to_form_example_5 (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 29, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 30, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding append_to_form_example_5 (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 31, + "content": "One important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of append_to_form_example_5 (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 32, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of append_to_form_example_5 (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '? 'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 33, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules append_to_form_example_5 (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 34, + "content": "To actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule append_to_form_example_5 (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 35, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable append_to_form_example_5 (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable append_to_form_example_5 (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index append_to_form_example_5 (define (use-index? pat) (constant-symbol? (car pat)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 36, + "content": "The query system uses a few stream operations that were not presented in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 37, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed append_to_form_example_5 (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 38, + "content": "Stream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed append_to_form_example_5 (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 39, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream append_to_form_example_5 (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 40, + "content": "We saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation. Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 41, + "content": "The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 42, + "content": "Unique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 43, + "content": "The function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 append_to_form_example_5 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ? processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 44, + "content": "An exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 45, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert append_to_form_example_5 function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ . express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ? make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form_example_5 append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ . Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 46, + "content": "The function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise . A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists. In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 47, + "content": "The functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type append_to_form_example_5 functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ? tail(exp) : error(exp, \"unknown expression contents\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 48, + "content": "The following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type append_to_form_example_5 function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 49, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction append_to_form_example_5 function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 50, + "content": "The following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 append_to_form_example_5 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 51, + "content": "Type and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme append_to_form_example_5 functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 52, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type append_to_form_example_5 (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 53, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme append_to_form_example_5 (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 54, + "content": "The following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 append_to_form_example_5 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 55, + "content": "Query-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 56, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 append_to_form_example_5 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 57, + "content": "Unique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id append_to_form_example_5 (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 58, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 59, + "content": "Frames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }" + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 1, + "content": "In chapter we stressed that computer science deals with" + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 2, + "content": "Most programming languages, including Lisp, JavaScript, are organized around computing the values of mathematical functions. Expression-oriented languages (such as Lisp, Fortran, Algol and JavaScript) (such as Lisp, C, Python, and JavaScript) capitalize on the pun that an expression that describes the value of a function may also be interpreted as a means of computing that value. Because of this, most programming languages are strongly biased toward unidirectional computations (computations with well-defined inputs and outputs). There are, however, radically different programming languages that relax this bias. We saw one such example in section , where the objects of computation were arithmetic constraints. In a constraint system the direction and the order of computation are not so well specified; in carrying out a computation the system must therefore provide more detailed how to knowledge than would be the case with an ordinary arithmetic computation. This does not mean, however, that the user is released altogether from the responsibility of providing imperative knowledge. There are many constraint networks that implement the same set of constraints, and the user must choose from the set of mathematically equivalent networks a suitable network to specify a particular computation." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 3, + "content": "The nondeterministic program evaluator of section also moves away from the view that programming is about constructing algorithms for computing unidirectional functions. In a nondeterministic language, expressions can have more than one value, and, as a result, the computation is dealing with unification ." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 4, + "content": "This approach, when it works, can be a very what is fact can be used to solve a number of different problems that would have different how to components. As an example, consider the append operation, which takes two lists as arguments and combines their elements to form a single list. In a procedural language such as Lisp, JavaScript, we could define append in terms of the basic list constructor cons , pair , as we did in section : (define (append x y) (if (null? x) y (cons (car x) (append (cdr x) y)))) function append(x, y) { return is_null(x) ? y : pair(head(x), append(tail(x), y)); } This procedure function can be regarded as a translation into Lisp JavaScript of the following two rules, the first of which covers the case where the first list is empty and the second of which handles the case of a nonempty list, which is a cons pair of two parts: For any list y , the empty list and y append to form y . For any u , v , y , and z , (cons u v) pair(u, v) and y append to form (cons u z) pair(u, z) if v and y append to form z . Using the append procedure, function, we can answer questions such as Find the append of (a b) list(\"a\", \"b\") and (c d) . list(\"c\", \"d\") . But the same two rules are also sufficient for answering the following sorts of questions, which the procedure function can t answer: Find a list y that append s with (a b) list(\"a\", \"b\") to produce (a b c d) . list(\"a\", \"b\", \"c\", \"d\") . Find all x and y that append to form (a b c d) . list(\"a\", \"b\", \"c\", \"d\") . In a append procedure function by stating the two rules about append given above. How to knowledge is provided automatically by the interpreter to allow this single pair of rules to be used to answer all three types of questions about append ." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 5, + "content": "Contemporary logic programming languages (including the one we implement here) have substantial deficiencies, in that their general how to methods can lead them into spurious infinite loops or other undesirable behavior. Logic programming is an active field of research in computer science." + }, + { + "source_file": "section4.xml", + "tag_type": "SECTION", + "title": "Logic Programming", + "parent_title": "Metalinguistic Abstraction", + "depth": 1, + "paragraph_index": 6, + "content": "Earlier in this chapter we explored the technology of implementing interpreters and described the elements that are essential to an interpreter for a Lisp-like JavaScript-like language (indeed, to an interpreter for any conventional language). Now we will apply these ideas to discuss an interpreter for a logic programming language. We call this language the query language , because it is very useful for retrieving information from data bases by formulating queries , or questions, expressed in the language. Even though the query language is very different from Lisp, JavaScript, we will find it convenient to describe the language in terms of the same general framework we have been using all along: as a collection of primitive elements, together with means of combination that enable us to combine simple elements to create more complex elements and means of abstraction that enable us to regard complex elements as single conceptual units. An interpreter for a logic programming language is considerably more complex than an interpreter for a language like Lisp. JavaScript. Nevertheless, we will see that our . In particular, there will be an evaluate part that classifies expressions according to type and an apply part that implements the language s abstraction mechanism (procedures (functions in the case of Lisp, JavaScript, and rules in the case of logic programming). Also, a central role is played in the implementation by a frame data structure, which determines the correspondence between symbols and their associated values. One additional interesting aspect of our query-language implementation is that we make substantial use of streams, which were introduced in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read query_driver_loop_example (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } query_driver_loop_example append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) process_query_example_1 parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop process_query_example_1 function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section )." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 4, + "content": "To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express append_to_form_example_5 (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 5, + "content": "The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query . evaluate_query operation_table_from_chapter_3 operation_table simple_query type append_to_form_example_5 (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 6, + "content": "The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query. simple_query stream_flatmap find_assertions apply_rules append_to_form_example_5 (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 7, + "content": "For each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 8, + "content": "And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin append_to_form_example_5 (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 9, + "content": "Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .) disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 11, + "content": "Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream append_to_form_example_5 (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null? (qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 12, + "content": "Lisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation. compound_queries_5_example compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream compound_queries_5_example (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Execute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system. execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction compound_queries_5_example (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 14, + "content": "The always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 15, + "content": "Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame. The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions append_to_form_example_5 (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 16, + "content": "Check-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream append_to_form_example_5 (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq? match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable append_to_form_example_5 (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var? pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 17, + "content": "Here is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding append_to_form_example_5 (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame. If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) . We never modify a stored binding and we never store more than one binding for a given variable." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 18, + "content": "The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 19, + "content": "If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 20, + "content": "When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 21, + "content": "Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list. For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 22, + "content": "Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules append_to_form_example_5 (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 23, + "content": "Apply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 24, + "content": "Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other. For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule append_to_form_example_5 (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 25, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 append_to_form_example_5 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 26, + "content": "The procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable append_to_form_example_5 (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 27, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 28, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable append_to_form_example_5 (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 29, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 30, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding append_to_form_example_5 (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 31, + "content": "One important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of append_to_form_example_5 (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 32, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of append_to_form_example_5 (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '? 'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 33, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules append_to_form_example_5 (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 34, + "content": "To actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule append_to_form_example_5 (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 35, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable append_to_form_example_5 (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable append_to_form_example_5 (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index append_to_form_example_5 (define (use-index? pat) (constant-symbol? (car pat)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 36, + "content": "The query system uses a few stream operations that were not presented in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 37, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed append_to_form_example_5 (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 38, + "content": "Stream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed append_to_form_example_5 (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 39, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream append_to_form_example_5 (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 40, + "content": "We saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation. Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 41, + "content": "The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 42, + "content": "Unique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 43, + "content": "The function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 append_to_form_example_5 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ? processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 44, + "content": "An exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 45, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert append_to_form_example_5 function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ . express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ? make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form_example_5 append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ . Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 46, + "content": "The function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise . A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists. In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 47, + "content": "The functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type append_to_form_example_5 functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ? tail(exp) : error(exp, \"unknown expression contents\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 48, + "content": "The following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type append_to_form_example_5 function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 49, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction append_to_form_example_5 function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 50, + "content": "The following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 append_to_form_example_5 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 51, + "content": "Type and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme append_to_form_example_5 functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 52, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type append_to_form_example_5 (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 53, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme append_to_form_example_5 (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 54, + "content": "The following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 append_to_form_example_5 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 55, + "content": "Query-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 56, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 append_to_form_example_5 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 57, + "content": "Unique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id append_to_form_example_5 (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 58, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 59, + "content": "Frames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read query_driver_loop_example (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } query_driver_loop_example append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) process_query_example_1 parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop process_query_example_1 function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section )." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 4, + "content": "To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express append_to_form_example_5 (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 5, + "content": "The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query . evaluate_query operation_table_from_chapter_3 operation_table simple_query type append_to_form_example_5 (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 6, + "content": "The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query. simple_query stream_flatmap find_assertions apply_rules append_to_form_example_5 (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 7, + "content": "For each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 8, + "content": "And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin append_to_form_example_5 (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 9, + "content": "Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .) disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 11, + "content": "Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream append_to_form_example_5 (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null? (qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 12, + "content": "Lisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation. compound_queries_5_example compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream compound_queries_5_example (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Execute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system. execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction compound_queries_5_example (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 14, + "content": "The always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 15, + "content": "Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame. The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions append_to_form_example_5 (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 16, + "content": "Check-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream append_to_form_example_5 (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq? match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable append_to_form_example_5 (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var? pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 17, + "content": "Here is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding append_to_form_example_5 (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame. If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) . We never modify a stored binding and we never store more than one binding for a given variable." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 18, + "content": "The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 19, + "content": "If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 20, + "content": "When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 21, + "content": "Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list. For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 22, + "content": "Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules append_to_form_example_5 (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 23, + "content": "Apply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 24, + "content": "Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other. For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule append_to_form_example_5 (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 25, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 append_to_form_example_5 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 26, + "content": "The procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable append_to_form_example_5 (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 27, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 28, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable append_to_form_example_5 (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 29, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 30, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding append_to_form_example_5 (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 31, + "content": "One important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of append_to_form_example_5 (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 32, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of append_to_form_example_5 (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '? 'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 33, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules append_to_form_example_5 (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 34, + "content": "To actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule append_to_form_example_5 (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 35, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable append_to_form_example_5 (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable append_to_form_example_5 (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index append_to_form_example_5 (define (use-index? pat) (constant-symbol? (car pat)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 36, + "content": "The query system uses a few stream operations that were not presented in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 37, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed append_to_form_example_5 (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 38, + "content": "Stream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed append_to_form_example_5 (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 39, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream append_to_form_example_5 (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 40, + "content": "We saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation. Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 41, + "content": "The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 42, + "content": "Unique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 43, + "content": "The function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 append_to_form_example_5 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ? processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 44, + "content": "An exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 45, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert append_to_form_example_5 function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ . express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ? make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form_example_5 append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ . Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 46, + "content": "The function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise . A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists. In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 47, + "content": "The functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type append_to_form_example_5 functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ? tail(exp) : error(exp, \"unknown expression contents\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 48, + "content": "The following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type append_to_form_example_5 function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 49, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction append_to_form_example_5 function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 50, + "content": "The following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 append_to_form_example_5 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 51, + "content": "Type and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme append_to_form_example_5 functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 52, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type append_to_form_example_5 (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 53, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme append_to_form_example_5 (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 54, + "content": "The following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 append_to_form_example_5 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 55, + "content": "Query-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 56, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 append_to_form_example_5 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 57, + "content": "Unique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id append_to_form_example_5 (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 58, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 59, + "content": "Frames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read query_driver_loop_example (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } query_driver_loop_example append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) process_query_example_1 parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop process_query_example_1 function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section )." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 4, + "content": "To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express append_to_form_example_5 (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 5, + "content": "The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query . evaluate_query operation_table_from_chapter_3 operation_table simple_query type append_to_form_example_5 (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 6, + "content": "The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query. simple_query stream_flatmap find_assertions apply_rules append_to_form_example_5 (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 7, + "content": "For each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 8, + "content": "And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin append_to_form_example_5 (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 9, + "content": "Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .) disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 11, + "content": "Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream append_to_form_example_5 (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null? (qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 12, + "content": "Lisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation. compound_queries_5_example compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream compound_queries_5_example (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Execute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system. execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction compound_queries_5_example (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 14, + "content": "The always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 15, + "content": "Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame. The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions append_to_form_example_5 (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 16, + "content": "Check-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream append_to_form_example_5 (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq? match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable append_to_form_example_5 (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var? pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 17, + "content": "Here is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding append_to_form_example_5 (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame. If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) . We never modify a stored binding and we never store more than one binding for a given variable." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 18, + "content": "The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 19, + "content": "If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 20, + "content": "When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 21, + "content": "Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list. For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 22, + "content": "Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules append_to_form_example_5 (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 23, + "content": "Apply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 24, + "content": "Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other. For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule append_to_form_example_5 (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 25, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 append_to_form_example_5 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 26, + "content": "The procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable append_to_form_example_5 (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 27, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 28, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable append_to_form_example_5 (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 29, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 30, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding append_to_form_example_5 (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 31, + "content": "One important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of append_to_form_example_5 (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 32, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of append_to_form_example_5 (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '? 'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 33, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules append_to_form_example_5 (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 34, + "content": "To actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule append_to_form_example_5 (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 35, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable append_to_form_example_5 (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable append_to_form_example_5 (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index append_to_form_example_5 (define (use-index? pat) (constant-symbol? (car pat)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 36, + "content": "The query system uses a few stream operations that were not presented in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 37, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed append_to_form_example_5 (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 38, + "content": "Stream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed append_to_form_example_5 (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 39, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream append_to_form_example_5 (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 40, + "content": "We saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation. Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 41, + "content": "The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 42, + "content": "Unique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 43, + "content": "The function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 append_to_form_example_5 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ? processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 44, + "content": "An exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 45, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert append_to_form_example_5 function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ . express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ? make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form_example_5 append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ . Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 46, + "content": "The function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise . A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists. In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 47, + "content": "The functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type append_to_form_example_5 functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ? tail(exp) : error(exp, \"unknown expression contents\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 48, + "content": "The following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type append_to_form_example_5 function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 49, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction append_to_form_example_5 function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 50, + "content": "The following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 append_to_form_example_5 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 51, + "content": "Type and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme append_to_form_example_5 functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 52, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type append_to_form_example_5 (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 53, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme append_to_form_example_5 (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 54, + "content": "The following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 append_to_form_example_5 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 55, + "content": "Query-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 56, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 append_to_form_example_5 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 57, + "content": "Unique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id append_to_form_example_5 (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 58, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 59, + "content": "Frames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read query_driver_loop_example (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } query_driver_loop_example append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) process_query_example_1 parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop process_query_example_1 function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section )." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 4, + "content": "To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express append_to_form_example_5 (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 5, + "content": "The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query . evaluate_query operation_table_from_chapter_3 operation_table simple_query type append_to_form_example_5 (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 6, + "content": "The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query. simple_query stream_flatmap find_assertions apply_rules append_to_form_example_5 (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 7, + "content": "For each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 8, + "content": "And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames. First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin append_to_form_example_5 (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 9, + "content": "Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .) disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed append_to_form_example_5 (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 10, + "content": "The predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 11, + "content": "Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream append_to_form_example_5 (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null? (qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 12, + "content": "Lisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation. compound_queries_5_example compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream compound_queries_5_example (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 13, + "content": "Execute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system. execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction compound_queries_5_example (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 14, + "content": "The always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 15, + "content": "Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame. The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions append_to_form_example_5 (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 16, + "content": "Check-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream append_to_form_example_5 (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq? match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable append_to_form_example_5 (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var? pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 17, + "content": "Here is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding append_to_form_example_5 (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame. If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) . We never modify a stored binding and we never store more than one binding for a given variable." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 18, + "content": "The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 19, + "content": "If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 20, + "content": "When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 21, + "content": "Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list. For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 22, + "content": "Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules append_to_form_example_5 (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 23, + "content": "Apply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 24, + "content": "Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other. For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule append_to_form_example_5 (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 25, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 append_to_form_example_5 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 26, + "content": "The procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable append_to_form_example_5 (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 27, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 28, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable append_to_form_example_5 (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 29, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 30, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding append_to_form_example_5 (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 31, + "content": "One important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of append_to_form_example_5 (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 32, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of append_to_form_example_5 (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '? 'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 33, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules append_to_form_example_5 (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 34, + "content": "To actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule append_to_form_example_5 (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 35, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable append_to_form_example_5 (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable append_to_form_example_5 (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index append_to_form_example_5 (define (use-index? pat) (constant-symbol? (car pat)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 36, + "content": "The query system uses a few stream operations that were not presented in chapter ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 37, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed append_to_form_example_5 (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 38, + "content": "Stream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed append_to_form_example_5 (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 39, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream append_to_form_example_5 (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 40, + "content": "We saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation. Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 41, + "content": "The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 42, + "content": "Unique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 43, + "content": "The function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 append_to_form_example_5 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ? processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 44, + "content": "An exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 ." + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 45, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert append_to_form_example_5 function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ . express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ? make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form_example_5 append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ . Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 46, + "content": "The function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise . A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists. In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 47, + "content": "The functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type append_to_form_example_5 functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ? tail(exp) : error(exp, \"unknown expression contents\"); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 48, + "content": "The following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type append_to_form_example_5 function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 49, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction append_to_form_example_5 function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 50, + "content": "The following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 append_to_form_example_5 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 51, + "content": "Type and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme append_to_form_example_5 functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 52, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type append_to_form_example_5 (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 53, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme append_to_form_example_5 (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 54, + "content": "The following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 append_to_form_example_5 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 55, + "content": "Query-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 56, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 append_to_form_example_5 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 57, + "content": "Unique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id append_to_form_example_5 (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 58, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))" + }, + { + "source_file": "subsection4.xml", + "tag_type": "SUBSECTION", + "title": "Implementing the Query System", + "parent_title": "Logic Programming", + "depth": 2, + "paragraph_index": 59, + "content": "Frames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table append_to_form_example_5 (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }" + } +] \ No newline at end of file diff --git a/parser/chapter5_chunks.json b/parser/chapter5_chunks.json new file mode 100644 index 000000000..644ea8ed7 --- /dev/null +++ b/parser/chapter5_chunks.json @@ -0,0 +1,2468 @@ +[ + { + "source_file": "chapter5.xml", + "tag_type": "CHAPTER", + "title": "Computing with Register Machines", + "parent_title": null, + "depth": 0, + "paragraph_index": 1, + "content": "We began this book by studying processes and by describing processes in terms of procedures functions written in Lisp. JavaScript. To explain the meanings of these procedures, functions, we used a succession of models of evaluation: the substitution model of chapter , the environment model of chapter , and the metacircular evaluator of chapter . Our examination of the metacircular evaluator, in particular, dispelled much of the mystery of how Lisp-like languages are interpreted. JavaScript-like languages are interpreted. But even the metacircular evaluator leaves important questions unanswered, because it fails to elucidate the mechanisms of control in a Lisp JavaScript system. For instance, the evaluator does not explain how the evaluation of a subexpression manages to return a value to the expression that uses this value , nor does the evaluator explain how some recursive procedures generate iterative processes (that is, are evaluated using constant space) whereas other recursive procedures generate recursive processes . These questions remain unanswered because the metacircular evaluator is itself a Lisp program and hence inherits the control structure of the underlying Lisp system. In order to provide a more complete description of the control structure of the Lisp evaluator, we must work at a more primitive level than Lisp itself. Also, the evaluator does not explain how some recursive functions can generate iterative processes (that is, be evaluated using constant space) whereas other recursive functions will generate recursive processes." + }, + { + "source_file": "chapter5.xml", + "tag_type": "CHAPTER", + "title": "Computing with Register Machines", + "parent_title": null, + "depth": 0, + "paragraph_index": 2, + "content": "In this chapter we We will describe processes in terms of the step-by-step operation of a traditional computer. Such a computer, or register machine , sequentially executes instructions that manipulate the contents of a fixed set of storage elements called registers . A typical register-machine instruction applies a primitive operation to the contents of some registers and assigns the result to another register. Our descriptions of processes executed by register machines will look very much like machine-language programs for traditional computers. However, instead of focusing on the machine language of any particular computer, we will examine several Lisp JavaScript procedures functions and design a specific register machine to execute each procedure. function. Thus, we will approach our task from the perspective of a hardware architect rather than that of a machine-language computer programmer. In designing register machines, we will develop mechanisms for implementing important programming constructs such as recursion. We will also present a language for describing designs for register machines. In section we will implement a Lisp JavaScript program that uses these descriptions to simulate the machines we design." + }, + { + "source_file": "chapter5.xml", + "tag_type": "CHAPTER", + "title": "Computing with Register Machines", + "parent_title": null, + "depth": 0, + "paragraph_index": 3, + "content": "Most of the primitive operations of our register machines are very simple. For example, an operation might add the numbers fetched from two registers, producing a result to be stored into a third register. Such an operation can be performed by easily described hardware. In order to deal with list structure, however, we will also use the memory operations car , head , cdr , tail , and cons , pair , which require an elaborate storage-allocation mechanism. In section we study their implementation in terms of more elementary operations." + }, + { + "source_file": "chapter5.xml", + "tag_type": "CHAPTER", + "title": "Computing with Register Machines", + "parent_title": null, + "depth": 0, + "paragraph_index": 4, + "content": "In section , after we have accumulated experience formulating simple procedures functions as register machines, we will design a machine that carries out the algorithm described by the metacircular evaluator of section . This will fill in the gap in our understanding of how Scheme expressions JavaScript programs are interpreted, by providing an explicit model for the mechanisms of control in the evaluator. In section we will study a simple compiler that translates Scheme JavaScript programs into sequences of instructions that can be executed directly with the registers and operations of the evaluator register machine." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 1, + "content": "The explicit-control evaluator of section is a register machine whose controller interprets Scheme JavaScript programs. In this section we will see how to run Scheme JavaScript programs on a register machine whose controller is not a Scheme JavaScript interpreter." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 2, + "content": "The explicit-control evaluator machine is it can carry out any computational process that can be described in Scheme. JavaScript. The evaluator s controller orchestrates the use of its data paths to perform the desired computation. Thus, the evaluator s data paths are universal: They are sufficient to perform any computation we desire, given an appropriate controller." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 3, + "content": "Commercial native language of the machine, or simply machine language . Programs written in machine language are sequences of instructions that use the machine s data paths. For example, the s instruction sequence can be thought of as a machine-language program for a general-purpose computer rather than as the controller for a specialized interpreter machine." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 4, + "content": "There are two common strategies for bridging the gap between higher-level languages and register-machine languages. The explicit-control evaluator illustrates the strategy of interpretation. An interpreter written in the native language of a machine configures the machine to execute programs written in a language (called the source language ) that may differ from the native language of the machine performing the evaluation. The primitive procedures functions of the source language are implemented as a library of subroutines written in the native language of the given machine. A program to be interpreted (called the source program ) is represented as a data structure. The interpreter traverses this data structure, analyzing the source program. As it does so, it simulates the intended behavior of the source program by calling appropriate primitive subroutines from the library." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 5, + "content": "In this section, we explore the alternative strategy of compilation . A compiler for a given source language and machine translates a source program into an equivalent program (called the object program ) written in the machine s native language. The compiler that we implement in this section translates programs written in Scheme JavaScript into sequences of instructions to be executed using the explicit-control evaluator machine s data paths." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 6, + "content": "Compared with interpretation, compilation can provide a great increase in the efficiency of program execution, as we will explain below in the overview of the compiler. On the other hand, an interpreter provides a more powerful environment for interactive program development and debugging, because the source program being executed is available at run time to be examined and modified. In addition, because the entire library of primitives is present, new programs can be constructed and added to the system during debugging." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 7, + "content": "In view of the complementary advantages of compilation and interpretation, modern program-development environments pursue a mixed strategy. Lisp interpreters These systems are generally organized so that interpreted procedures functions and compiled procedures functions can call each other. This enables a programmer to compile those parts of a program that are assumed to be debugged, thus gaining the efficiency advantage of compilation, while retaining the interpretive mode of execution for those parts of the program that are in the flux of interactive development and debugging. In section , after we have implemented the compiler, we will show how to interface it with our interpreter to produce an integrated interpreter-compiler development system." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 8, + "content": "Our compiler is much like our interpreter, both in its structure and in the function it performs. Accordingly, the mechanisms used by the compiler for analyzing expressions components will be similar to those used by the interpreter. Moreover, to make it easy to interface compiled and interpreted code, we will design the compiler to generate code that obeys the same conventions of env register, argument lists will be accumulated in argl , a procedure function to be applied will be in proc , fun , procedures functions will return their answers in val , and the location to which a procedure function should return will be kept in continue . In general, the compiler translates a source program into an object program that performs essentially the same register operations as would the interpreter in evaluating the same source program." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 9, + "content": "This description suggests a strategy for implementing a rudimentary compiler: We traverse the expression component in the same way the interpreter does. When we encounter a register instruction that the interpreter would perform in evaluating the expression, component, we do not execute the instruction but instead accumulate it into a sequence. The resulting sequence of instructions will be the object code. Observe the an expression for example, a component for example, (f 84 96) it f(96, 22) it performs the work of classifying the expression component (discovering that this is a procedure function application) and testing for the end of the operand list (discovering that there are two operands). list of argument expressions (discovering that there are two argument expressions). With a compiler, the expression component is analyzed only once, when the instruction sequence is generated at compile time. The object code produced by the compiler contains only the instructions that evaluate the operator and the two operands, function expression and the two argument expressions, assemble the argument list, and apply the procedure (in proc ) function (in fun ) to the arguments (in argl )." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 10, + "content": "This is the same kind of optimization we implemented in the . But there are further opportunities to gain efficiency in compiled code. As the interpreter runs, it follows a process that must be applicable to any expression component in the language. In contrast, a given segment of compiled code is meant to execute some particular expression. component. This can make a big difference, for example in the use of the stack to save registers. When the interpreter evaluates an expression, a component, it must be prepared for any contingency. Before evaluating a subexpression, subcomponent, the interpreter saves all registers that will be needed later, because the subexpression subcomponent might require an arbitrary evaluation. A compiler, on the other hand, can exploit the structure of the particular expression component it is processing to generate code that avoids unnecessary stack operations." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 11, + "content": "As a case in point, consider the combination (f 84 96) . application f(96, 22) . Before the interpreter evaluates the operator of the combination, function expression of the application, it prepares for this evaluation by saving the registers containing the operands argument expressions and the environment, whose values will be needed later. The interpreter then evaluates the operator function expression to obtain the result in val , restores the saved registers, and finally moves the result from val to proc . fun . However, in the particular expression we are dealing with, the operator function expression is the symbol name f , whose evaluation is accomplished by the machine operation lookup-variable-value , lookup_symbol_value , which does not alter any registers. The compiler that we implement in this section will take advantage of this fact and generate code that evaluates the operator function expression using the instruction (assign proc (op lookup-variable-value) (const f) (reg env)) assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"f\"), reg(\"env\"))) where the argument to lookup_symbol_value is extracted at compile time from the parser's representation of f(96, 22) . This code not only avoids the unnecessary saves and restores but also assigns the value of the lookup directly to proc , fun , whereas the interpreter would obtain the result in val and then move this to proc . fun ." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 12, + "content": "A compiler can also optimize access to the environment. Having analyzed the code, the compiler can in many cases know in which frame a particular variable the value of a particular name will be located and access that frame directly, rather than performing the lookup-variable-value lookup_@symbol_@value search. We will discuss how to implement such variable access lexical addressing in section . Until then, however, we will focus on the kind of register and stack optimizations described above. There are many other optimizations that can be performed by a compiler, such as coding primitive operations in line instead of using a general apply mechanism (see exercise ); but we will not emphasize these here. Our main goal in this section is to illustrate the compilation process in a simplified (but still interesting) context." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 1, + "content": "The explicit-control evaluator of section is a register machine whose controller interprets Scheme JavaScript programs. In this section we will see how to run Scheme JavaScript programs on a register machine whose controller is not a Scheme JavaScript interpreter." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 2, + "content": "The explicit-control evaluator machine is it can carry out any computational process that can be described in Scheme. JavaScript. The evaluator s controller orchestrates the use of its data paths to perform the desired computation. Thus, the evaluator s data paths are universal: They are sufficient to perform any computation we desire, given an appropriate controller." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 3, + "content": "Commercial native language of the machine, or simply machine language . Programs written in machine language are sequences of instructions that use the machine s data paths. For example, the s instruction sequence can be thought of as a machine-language program for a general-purpose computer rather than as the controller for a specialized interpreter machine." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 4, + "content": "There are two common strategies for bridging the gap between higher-level languages and register-machine languages. The explicit-control evaluator illustrates the strategy of interpretation. An interpreter written in the native language of a machine configures the machine to execute programs written in a language (called the source language ) that may differ from the native language of the machine performing the evaluation. The primitive procedures functions of the source language are implemented as a library of subroutines written in the native language of the given machine. A program to be interpreted (called the source program ) is represented as a data structure. The interpreter traverses this data structure, analyzing the source program. As it does so, it simulates the intended behavior of the source program by calling appropriate primitive subroutines from the library." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 5, + "content": "In this section, we explore the alternative strategy of compilation . A compiler for a given source language and machine translates a source program into an equivalent program (called the object program ) written in the machine s native language. The compiler that we implement in this section translates programs written in Scheme JavaScript into sequences of instructions to be executed using the explicit-control evaluator machine s data paths." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 6, + "content": "Compared with interpretation, compilation can provide a great increase in the efficiency of program execution, as we will explain below in the overview of the compiler. On the other hand, an interpreter provides a more powerful environment for interactive program development and debugging, because the source program being executed is available at run time to be examined and modified. In addition, because the entire library of primitives is present, new programs can be constructed and added to the system during debugging." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 7, + "content": "In view of the complementary advantages of compilation and interpretation, modern program-development environments pursue a mixed strategy. Lisp interpreters These systems are generally organized so that interpreted procedures functions and compiled procedures functions can call each other. This enables a programmer to compile those parts of a program that are assumed to be debugged, thus gaining the efficiency advantage of compilation, while retaining the interpretive mode of execution for those parts of the program that are in the flux of interactive development and debugging. In section , after we have implemented the compiler, we will show how to interface it with our interpreter to produce an integrated interpreter-compiler development system." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 8, + "content": "Our compiler is much like our interpreter, both in its structure and in the function it performs. Accordingly, the mechanisms used by the compiler for analyzing expressions components will be similar to those used by the interpreter. Moreover, to make it easy to interface compiled and interpreted code, we will design the compiler to generate code that obeys the same conventions of env register, argument lists will be accumulated in argl , a procedure function to be applied will be in proc , fun , procedures functions will return their answers in val , and the location to which a procedure function should return will be kept in continue . In general, the compiler translates a source program into an object program that performs essentially the same register operations as would the interpreter in evaluating the same source program." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 9, + "content": "This description suggests a strategy for implementing a rudimentary compiler: We traverse the expression component in the same way the interpreter does. When we encounter a register instruction that the interpreter would perform in evaluating the expression, component, we do not execute the instruction but instead accumulate it into a sequence. The resulting sequence of instructions will be the object code. Observe the an expression for example, a component for example, (f 84 96) it f(96, 22) it performs the work of classifying the expression component (discovering that this is a procedure function application) and testing for the end of the operand list (discovering that there are two operands). list of argument expressions (discovering that there are two argument expressions). With a compiler, the expression component is analyzed only once, when the instruction sequence is generated at compile time. The object code produced by the compiler contains only the instructions that evaluate the operator and the two operands, function expression and the two argument expressions, assemble the argument list, and apply the procedure (in proc ) function (in fun ) to the arguments (in argl )." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 10, + "content": "This is the same kind of optimization we implemented in the . But there are further opportunities to gain efficiency in compiled code. As the interpreter runs, it follows a process that must be applicable to any expression component in the language. In contrast, a given segment of compiled code is meant to execute some particular expression. component. This can make a big difference, for example in the use of the stack to save registers. When the interpreter evaluates an expression, a component, it must be prepared for any contingency. Before evaluating a subexpression, subcomponent, the interpreter saves all registers that will be needed later, because the subexpression subcomponent might require an arbitrary evaluation. A compiler, on the other hand, can exploit the structure of the particular expression component it is processing to generate code that avoids unnecessary stack operations." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 11, + "content": "As a case in point, consider the combination (f 84 96) . application f(96, 22) . Before the interpreter evaluates the operator of the combination, function expression of the application, it prepares for this evaluation by saving the registers containing the operands argument expressions and the environment, whose values will be needed later. The interpreter then evaluates the operator function expression to obtain the result in val , restores the saved registers, and finally moves the result from val to proc . fun . However, in the particular expression we are dealing with, the operator function expression is the symbol name f , whose evaluation is accomplished by the machine operation lookup-variable-value , lookup_symbol_value , which does not alter any registers. The compiler that we implement in this section will take advantage of this fact and generate code that evaluates the operator function expression using the instruction (assign proc (op lookup-variable-value) (const f) (reg env)) assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"f\"), reg(\"env\"))) where the argument to lookup_symbol_value is extracted at compile time from the parser's representation of f(96, 22) . This code not only avoids the unnecessary saves and restores but also assigns the value of the lookup directly to proc , fun , whereas the interpreter would obtain the result in val and then move this to proc . fun ." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 12, + "content": "A compiler can also optimize access to the environment. Having analyzed the code, the compiler can in many cases know in which frame a particular variable the value of a particular name will be located and access that frame directly, rather than performing the lookup-variable-value lookup_@symbol_@value search. We will discuss how to implement such variable access lexical addressing in section . Until then, however, we will focus on the kind of register and stack optimizations described above. There are many other optimizations that can be performed by a compiler, such as coding primitive operations in line instead of using a general apply mechanism (see exercise ); but we will not emphasize these here. Our main goal in this section is to illustrate the compilation process in a simplified (but still interesting) context." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 1, + "content": "The explicit-control evaluator of section is a register machine whose controller interprets Scheme JavaScript programs. In this section we will see how to run Scheme JavaScript programs on a register machine whose controller is not a Scheme JavaScript interpreter." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 2, + "content": "The explicit-control evaluator machine is it can carry out any computational process that can be described in Scheme. JavaScript. The evaluator s controller orchestrates the use of its data paths to perform the desired computation. Thus, the evaluator s data paths are universal: They are sufficient to perform any computation we desire, given an appropriate controller." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 3, + "content": "Commercial native language of the machine, or simply machine language . Programs written in machine language are sequences of instructions that use the machine s data paths. For example, the s instruction sequence can be thought of as a machine-language program for a general-purpose computer rather than as the controller for a specialized interpreter machine." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 4, + "content": "There are two common strategies for bridging the gap between higher-level languages and register-machine languages. The explicit-control evaluator illustrates the strategy of interpretation. An interpreter written in the native language of a machine configures the machine to execute programs written in a language (called the source language ) that may differ from the native language of the machine performing the evaluation. The primitive procedures functions of the source language are implemented as a library of subroutines written in the native language of the given machine. A program to be interpreted (called the source program ) is represented as a data structure. The interpreter traverses this data structure, analyzing the source program. As it does so, it simulates the intended behavior of the source program by calling appropriate primitive subroutines from the library." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 5, + "content": "In this section, we explore the alternative strategy of compilation . A compiler for a given source language and machine translates a source program into an equivalent program (called the object program ) written in the machine s native language. The compiler that we implement in this section translates programs written in Scheme JavaScript into sequences of instructions to be executed using the explicit-control evaluator machine s data paths." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 6, + "content": "Compared with interpretation, compilation can provide a great increase in the efficiency of program execution, as we will explain below in the overview of the compiler. On the other hand, an interpreter provides a more powerful environment for interactive program development and debugging, because the source program being executed is available at run time to be examined and modified. In addition, because the entire library of primitives is present, new programs can be constructed and added to the system during debugging." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 7, + "content": "In view of the complementary advantages of compilation and interpretation, modern program-development environments pursue a mixed strategy. Lisp interpreters These systems are generally organized so that interpreted procedures functions and compiled procedures functions can call each other. This enables a programmer to compile those parts of a program that are assumed to be debugged, thus gaining the efficiency advantage of compilation, while retaining the interpretive mode of execution for those parts of the program that are in the flux of interactive development and debugging. In section , after we have implemented the compiler, we will show how to interface it with our interpreter to produce an integrated interpreter-compiler development system." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 8, + "content": "Our compiler is much like our interpreter, both in its structure and in the function it performs. Accordingly, the mechanisms used by the compiler for analyzing expressions components will be similar to those used by the interpreter. Moreover, to make it easy to interface compiled and interpreted code, we will design the compiler to generate code that obeys the same conventions of env register, argument lists will be accumulated in argl , a procedure function to be applied will be in proc , fun , procedures functions will return their answers in val , and the location to which a procedure function should return will be kept in continue . In general, the compiler translates a source program into an object program that performs essentially the same register operations as would the interpreter in evaluating the same source program." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 9, + "content": "This description suggests a strategy for implementing a rudimentary compiler: We traverse the expression component in the same way the interpreter does. When we encounter a register instruction that the interpreter would perform in evaluating the expression, component, we do not execute the instruction but instead accumulate it into a sequence. The resulting sequence of instructions will be the object code. Observe the an expression for example, a component for example, (f 84 96) it f(96, 22) it performs the work of classifying the expression component (discovering that this is a procedure function application) and testing for the end of the operand list (discovering that there are two operands). list of argument expressions (discovering that there are two argument expressions). With a compiler, the expression component is analyzed only once, when the instruction sequence is generated at compile time. The object code produced by the compiler contains only the instructions that evaluate the operator and the two operands, function expression and the two argument expressions, assemble the argument list, and apply the procedure (in proc ) function (in fun ) to the arguments (in argl )." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 10, + "content": "This is the same kind of optimization we implemented in the . But there are further opportunities to gain efficiency in compiled code. As the interpreter runs, it follows a process that must be applicable to any expression component in the language. In contrast, a given segment of compiled code is meant to execute some particular expression. component. This can make a big difference, for example in the use of the stack to save registers. When the interpreter evaluates an expression, a component, it must be prepared for any contingency. Before evaluating a subexpression, subcomponent, the interpreter saves all registers that will be needed later, because the subexpression subcomponent might require an arbitrary evaluation. A compiler, on the other hand, can exploit the structure of the particular expression component it is processing to generate code that avoids unnecessary stack operations." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 11, + "content": "As a case in point, consider the combination (f 84 96) . application f(96, 22) . Before the interpreter evaluates the operator of the combination, function expression of the application, it prepares for this evaluation by saving the registers containing the operands argument expressions and the environment, whose values will be needed later. The interpreter then evaluates the operator function expression to obtain the result in val , restores the saved registers, and finally moves the result from val to proc . fun . However, in the particular expression we are dealing with, the operator function expression is the symbol name f , whose evaluation is accomplished by the machine operation lookup-variable-value , lookup_symbol_value , which does not alter any registers. The compiler that we implement in this section will take advantage of this fact and generate code that evaluates the operator function expression using the instruction (assign proc (op lookup-variable-value) (const f) (reg env)) assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"f\"), reg(\"env\"))) where the argument to lookup_symbol_value is extracted at compile time from the parser's representation of f(96, 22) . This code not only avoids the unnecessary saves and restores but also assigns the value of the lookup directly to proc , fun , whereas the interpreter would obtain the result in val and then move this to proc . fun ." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 12, + "content": "A compiler can also optimize access to the environment. Having analyzed the code, the compiler can in many cases know in which frame a particular variable the value of a particular name will be located and access that frame directly, rather than performing the lookup-variable-value lookup_@symbol_@value search. We will discuss how to implement such variable access lexical addressing in section . Until then, however, we will focus on the kind of register and stack optimizations described above. There are many other optimizations that can be performed by a compiler, such as coding primitive operations in line instead of using a general apply mechanism (see exercise ); but we will not emphasize these here. Our main goal in this section is to illustrate the compilation process in a simplified (but still interesting) context." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 1, + "content": "The explicit-control evaluator of section is a register machine whose controller interprets Scheme JavaScript programs. In this section we will see how to run Scheme JavaScript programs on a register machine whose controller is not a Scheme JavaScript interpreter." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 2, + "content": "The explicit-control evaluator machine is it can carry out any computational process that can be described in Scheme. JavaScript. The evaluator s controller orchestrates the use of its data paths to perform the desired computation. Thus, the evaluator s data paths are universal: They are sufficient to perform any computation we desire, given an appropriate controller." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 3, + "content": "Commercial native language of the machine, or simply machine language . Programs written in machine language are sequences of instructions that use the machine s data paths. For example, the s instruction sequence can be thought of as a machine-language program for a general-purpose computer rather than as the controller for a specialized interpreter machine." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 4, + "content": "There are two common strategies for bridging the gap between higher-level languages and register-machine languages. The explicit-control evaluator illustrates the strategy of interpretation. An interpreter written in the native language of a machine configures the machine to execute programs written in a language (called the source language ) that may differ from the native language of the machine performing the evaluation. The primitive procedures functions of the source language are implemented as a library of subroutines written in the native language of the given machine. A program to be interpreted (called the source program ) is represented as a data structure. The interpreter traverses this data structure, analyzing the source program. As it does so, it simulates the intended behavior of the source program by calling appropriate primitive subroutines from the library." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 5, + "content": "In this section, we explore the alternative strategy of compilation . A compiler for a given source language and machine translates a source program into an equivalent program (called the object program ) written in the machine s native language. The compiler that we implement in this section translates programs written in Scheme JavaScript into sequences of instructions to be executed using the explicit-control evaluator machine s data paths." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 6, + "content": "Compared with interpretation, compilation can provide a great increase in the efficiency of program execution, as we will explain below in the overview of the compiler. On the other hand, an interpreter provides a more powerful environment for interactive program development and debugging, because the source program being executed is available at run time to be examined and modified. In addition, because the entire library of primitives is present, new programs can be constructed and added to the system during debugging." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 7, + "content": "In view of the complementary advantages of compilation and interpretation, modern program-development environments pursue a mixed strategy. Lisp interpreters These systems are generally organized so that interpreted procedures functions and compiled procedures functions can call each other. This enables a programmer to compile those parts of a program that are assumed to be debugged, thus gaining the efficiency advantage of compilation, while retaining the interpretive mode of execution for those parts of the program that are in the flux of interactive development and debugging. In section , after we have implemented the compiler, we will show how to interface it with our interpreter to produce an integrated interpreter-compiler development system." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 8, + "content": "Our compiler is much like our interpreter, both in its structure and in the function it performs. Accordingly, the mechanisms used by the compiler for analyzing expressions components will be similar to those used by the interpreter. Moreover, to make it easy to interface compiled and interpreted code, we will design the compiler to generate code that obeys the same conventions of env register, argument lists will be accumulated in argl , a procedure function to be applied will be in proc , fun , procedures functions will return their answers in val , and the location to which a procedure function should return will be kept in continue . In general, the compiler translates a source program into an object program that performs essentially the same register operations as would the interpreter in evaluating the same source program." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 9, + "content": "This description suggests a strategy for implementing a rudimentary compiler: We traverse the expression component in the same way the interpreter does. When we encounter a register instruction that the interpreter would perform in evaluating the expression, component, we do not execute the instruction but instead accumulate it into a sequence. The resulting sequence of instructions will be the object code. Observe the an expression for example, a component for example, (f 84 96) it f(96, 22) it performs the work of classifying the expression component (discovering that this is a procedure function application) and testing for the end of the operand list (discovering that there are two operands). list of argument expressions (discovering that there are two argument expressions). With a compiler, the expression component is analyzed only once, when the instruction sequence is generated at compile time. The object code produced by the compiler contains only the instructions that evaluate the operator and the two operands, function expression and the two argument expressions, assemble the argument list, and apply the procedure (in proc ) function (in fun ) to the arguments (in argl )." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 10, + "content": "This is the same kind of optimization we implemented in the . But there are further opportunities to gain efficiency in compiled code. As the interpreter runs, it follows a process that must be applicable to any expression component in the language. In contrast, a given segment of compiled code is meant to execute some particular expression. component. This can make a big difference, for example in the use of the stack to save registers. When the interpreter evaluates an expression, a component, it must be prepared for any contingency. Before evaluating a subexpression, subcomponent, the interpreter saves all registers that will be needed later, because the subexpression subcomponent might require an arbitrary evaluation. A compiler, on the other hand, can exploit the structure of the particular expression component it is processing to generate code that avoids unnecessary stack operations." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 11, + "content": "As a case in point, consider the combination (f 84 96) . application f(96, 22) . Before the interpreter evaluates the operator of the combination, function expression of the application, it prepares for this evaluation by saving the registers containing the operands argument expressions and the environment, whose values will be needed later. The interpreter then evaluates the operator function expression to obtain the result in val , restores the saved registers, and finally moves the result from val to proc . fun . However, in the particular expression we are dealing with, the operator function expression is the symbol name f , whose evaluation is accomplished by the machine operation lookup-variable-value , lookup_symbol_value , which does not alter any registers. The compiler that we implement in this section will take advantage of this fact and generate code that evaluates the operator function expression using the instruction (assign proc (op lookup-variable-value) (const f) (reg env)) assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"f\"), reg(\"env\"))) where the argument to lookup_symbol_value is extracted at compile time from the parser's representation of f(96, 22) . This code not only avoids the unnecessary saves and restores but also assigns the value of the lookup directly to proc , fun , whereas the interpreter would obtain the result in val and then move this to proc . fun ." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 12, + "content": "A compiler can also optimize access to the environment. Having analyzed the code, the compiler can in many cases know in which frame a particular variable the value of a particular name will be located and access that frame directly, rather than performing the lookup-variable-value lookup_@symbol_@value search. We will discuss how to implement such variable access lexical addressing in section . Until then, however, we will focus on the kind of register and stack optimizations described above. There are many other optimizations that can be performed by a compiler, such as coding primitive operations in line instead of using a general apply mechanism (see exercise ); but we will not emphasize these here. Our main goal in this section is to illustrate the compilation process in a simplified (but still interesting) context." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 1, + "content": "The explicit-control evaluator of section is a register machine whose controller interprets Scheme JavaScript programs. In this section we will see how to run Scheme JavaScript programs on a register machine whose controller is not a Scheme JavaScript interpreter." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 2, + "content": "The explicit-control evaluator machine is it can carry out any computational process that can be described in Scheme. JavaScript. The evaluator s controller orchestrates the use of its data paths to perform the desired computation. Thus, the evaluator s data paths are universal: They are sufficient to perform any computation we desire, given an appropriate controller." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 3, + "content": "Commercial native language of the machine, or simply machine language . Programs written in machine language are sequences of instructions that use the machine s data paths. For example, the s instruction sequence can be thought of as a machine-language program for a general-purpose computer rather than as the controller for a specialized interpreter machine." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 4, + "content": "There are two common strategies for bridging the gap between higher-level languages and register-machine languages. The explicit-control evaluator illustrates the strategy of interpretation. An interpreter written in the native language of a machine configures the machine to execute programs written in a language (called the source language ) that may differ from the native language of the machine performing the evaluation. The primitive procedures functions of the source language are implemented as a library of subroutines written in the native language of the given machine. A program to be interpreted (called the source program ) is represented as a data structure. The interpreter traverses this data structure, analyzing the source program. As it does so, it simulates the intended behavior of the source program by calling appropriate primitive subroutines from the library." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 5, + "content": "In this section, we explore the alternative strategy of compilation . A compiler for a given source language and machine translates a source program into an equivalent program (called the object program ) written in the machine s native language. The compiler that we implement in this section translates programs written in Scheme JavaScript into sequences of instructions to be executed using the explicit-control evaluator machine s data paths." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 6, + "content": "Compared with interpretation, compilation can provide a great increase in the efficiency of program execution, as we will explain below in the overview of the compiler. On the other hand, an interpreter provides a more powerful environment for interactive program development and debugging, because the source program being executed is available at run time to be examined and modified. In addition, because the entire library of primitives is present, new programs can be constructed and added to the system during debugging." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 7, + "content": "In view of the complementary advantages of compilation and interpretation, modern program-development environments pursue a mixed strategy. Lisp interpreters These systems are generally organized so that interpreted procedures functions and compiled procedures functions can call each other. This enables a programmer to compile those parts of a program that are assumed to be debugged, thus gaining the efficiency advantage of compilation, while retaining the interpretive mode of execution for those parts of the program that are in the flux of interactive development and debugging. In section , after we have implemented the compiler, we will show how to interface it with our interpreter to produce an integrated interpreter-compiler development system." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 8, + "content": "Our compiler is much like our interpreter, both in its structure and in the function it performs. Accordingly, the mechanisms used by the compiler for analyzing expressions components will be similar to those used by the interpreter. Moreover, to make it easy to interface compiled and interpreted code, we will design the compiler to generate code that obeys the same conventions of env register, argument lists will be accumulated in argl , a procedure function to be applied will be in proc , fun , procedures functions will return their answers in val , and the location to which a procedure function should return will be kept in continue . In general, the compiler translates a source program into an object program that performs essentially the same register operations as would the interpreter in evaluating the same source program." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 9, + "content": "This description suggests a strategy for implementing a rudimentary compiler: We traverse the expression component in the same way the interpreter does. When we encounter a register instruction that the interpreter would perform in evaluating the expression, component, we do not execute the instruction but instead accumulate it into a sequence. The resulting sequence of instructions will be the object code. Observe the an expression for example, a component for example, (f 84 96) it f(96, 22) it performs the work of classifying the expression component (discovering that this is a procedure function application) and testing for the end of the operand list (discovering that there are two operands). list of argument expressions (discovering that there are two argument expressions). With a compiler, the expression component is analyzed only once, when the instruction sequence is generated at compile time. The object code produced by the compiler contains only the instructions that evaluate the operator and the two operands, function expression and the two argument expressions, assemble the argument list, and apply the procedure (in proc ) function (in fun ) to the arguments (in argl )." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 10, + "content": "This is the same kind of optimization we implemented in the . But there are further opportunities to gain efficiency in compiled code. As the interpreter runs, it follows a process that must be applicable to any expression component in the language. In contrast, a given segment of compiled code is meant to execute some particular expression. component. This can make a big difference, for example in the use of the stack to save registers. When the interpreter evaluates an expression, a component, it must be prepared for any contingency. Before evaluating a subexpression, subcomponent, the interpreter saves all registers that will be needed later, because the subexpression subcomponent might require an arbitrary evaluation. A compiler, on the other hand, can exploit the structure of the particular expression component it is processing to generate code that avoids unnecessary stack operations." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 11, + "content": "As a case in point, consider the combination (f 84 96) . application f(96, 22) . Before the interpreter evaluates the operator of the combination, function expression of the application, it prepares for this evaluation by saving the registers containing the operands argument expressions and the environment, whose values will be needed later. The interpreter then evaluates the operator function expression to obtain the result in val , restores the saved registers, and finally moves the result from val to proc . fun . However, in the particular expression we are dealing with, the operator function expression is the symbol name f , whose evaluation is accomplished by the machine operation lookup-variable-value , lookup_symbol_value , which does not alter any registers. The compiler that we implement in this section will take advantage of this fact and generate code that evaluates the operator function expression using the instruction (assign proc (op lookup-variable-value) (const f) (reg env)) assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"f\"), reg(\"env\"))) where the argument to lookup_symbol_value is extracted at compile time from the parser's representation of f(96, 22) . This code not only avoids the unnecessary saves and restores but also assigns the value of the lookup directly to proc , fun , whereas the interpreter would obtain the result in val and then move this to proc . fun ." + }, + { + "source_file": "section5.xml", + "tag_type": "SECTION", + "title": "Compilation", + "parent_title": "Computing with Register Machines", + "depth": 1, + "paragraph_index": 12, + "content": "A compiler can also optimize access to the environment. Having analyzed the code, the compiler can in many cases know in which frame a particular variable the value of a particular name will be located and access that frame directly, rather than performing the lookup-variable-value lookup_@symbol_@value search. We will discuss how to implement such variable access lexical addressing in section . Until then, however, we will focus on the kind of register and stack optimizations described above. There are many other optimizations that can be performed by a compiler, such as coding primitive operations in line instead of using a general apply mechanism (see exercise ); but we will not emphasize these here. Our main goal in this section is to illustrate the compilation process in a simplified (but still interesting) context." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary." + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 2, + "content": "Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration . This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 3, + "content": "The expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials. Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))" + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 4, + "content": "A procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function. Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?) (reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 5, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\"," + }, + { + "source_file": "subsection5.xml", + "tag_type": "SUBSECTION", + "title": "An Example of Compiled Code", + "parent_title": "Compilation", + "depth": 2, + "paragraph_index": 6, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches." + } +] \ No newline at end of file diff --git a/parser/chunking.py b/parser/chunking.py new file mode 100644 index 000000000..663affc27 --- /dev/null +++ b/parser/chunking.py @@ -0,0 +1,238 @@ +import os +import re +import json +from glob import glob +from itertools import groupby +from difflib import SequenceMatcher +from typing import List, Tuple + +try: + import nltk + nltk.download("punkt", quiet=True) + try: + nltk.download("punkt_tab", quiet=True) + except Exception: + pass + _NLTK_OK = True +except Exception: + nltk = None + _NLTK_OK = False + +try: + import tiktoken + _ENC = tiktoken.get_encoding("cl100k_base") +except Exception: + _ENC = None + +# ---------- CONFIG ---------- +CHAPTER_JSON_DIR = "." +CHAPTER_FILE_GLOB = "chapter*_chunks.json" +OUTPUT_FILE = "sicp_mesochunks_semantic_rag.json" + +MAX_TOKENS = 300 # smaller chunks for RAG +OVERLAP_SENTENCES = 1 +HARD_SENT_WORD_SPLIT = 80 +HUGE_CODE_LINES = 25 +SYMBOL_DENSITY = 0.10 + +# ---------- HELPERS ---------- +def num_tokens(text: str) -> int: + if _ENC: + try: + return len(_ENC.encode(text)) + except Exception: + pass + return max(1, len(text.split())) + +def safe_sentence_tokenize(text: str) -> List[str]: + if _NLTK_OK and nltk: + try: + return nltk.sent_tokenize(text) + except Exception: + pass + parts = re.split(r'(?<=[.!?])\s+(?=[A-Z(0-9`])', text.strip()) + if len(parts) == 1: + words = text.split() + return [" ".join(words[i:i+HARD_SENT_WORD_SPLIT]) for i in range(0,len(words),HARD_SENT_WORD_SPLIT)] or [text] + return parts + +def is_symbol_dense(line: str) -> bool: + symbols = re.findall(r"[()\[\]{};:+\-*/=<>|&^%$~,.`]", line) + return (len(symbols) / max(1, len(line))) >= SYMBOL_DENSITY + +def looks_like_code_line(line: str) -> bool: + s = line.strip() + if not s: return False + if s == "```": return True + if s.startswith((" ", "\t")): return True + if re.match(r"^\s*(>>>|#|//|/\*|\*|\w+\s*=\s*)", s): return True + if re.match(r"^\s*\(.*\)\s*$", s): return True # Lisp + if re.match(r"^\s*(function\s+\w+\s*\(|\w+\s*\(.*\)\s*;?\s*|\{\s*|\}\s*)$", s): return True + if re.match(r"^\s*[\d.]+\s*[\+\-\*/]\s*[\d.]+;?\s*$", s): return True + if is_symbol_dense(s): return True + return False + +def detect_code_blocks(text: str) -> List[Tuple[str,str]]: + segs, cur, is_code, fenced = [], [], False, False + lines = text.splitlines() + for i, raw in enumerate(lines): + line = raw.rstrip("\n") + if line.strip() == "```": + if cur: + segs.append(("code" if is_code or fenced else "text", "\n".join(cur))) + cur = [] + fenced = not fenced + is_code = fenced or is_code + continue + + # decide next_line for boundary detection + next_line = lines[i+1].strip() if i+1 < len(lines) else "" + + if fenced or looks_like_code_line(line): + if not is_code and cur: + segs.append(("text", "\n".join(cur))) + cur = [] + is_code = True + else: + # boundary: code followed by capitalized prose + if is_code and next_line and re.match(r"^[A-Z]", next_line): + segs.append(("code", "\n".join(cur+[line]))) + cur, is_code = [], False + continue + if is_code and cur: + segs.append(("code", "\n".join(cur))) + cur = [] + is_code = False + cur.append(line) + if cur: + segs.append(("code" if is_code or fenced else "text", "\n".join(cur))) + return segs + +def split_code_by_lines(block: str, window: int) -> List[str]: + lines, out, cur = block.split("\n"), [], [] + for ln in lines: + cur.append(ln) + if len(cur) >= window or num_tokens("\n".join(cur)) >= MAX_TOKENS: + out.append("\n".join(cur)) + cur = [] + if cur: out.append("\n".join(cur)) + return out + +def split_huge_code_block(block: str) -> List[str]: + if "function" in block and "(define" in block: + return split_code_by_lines(block, 15) + lines = block.split("\n") + return split_code_by_lines(block, HUGE_CODE_LINES) + +def clean_text_noise(txt: str) -> str: + txt = re.sub(r'\b\w*_example(_\d+)?\b', '', txt) + lines = [l for l in txt.splitlines() if l.strip()] + uniq = [] + for l in lines: + if not uniq or l.strip() != uniq[-1].strip(): + uniq.append(l) + return "\n".join(uniq).strip() + +def is_duplicate_code(a: str, b: str) -> bool: + return SequenceMatcher(None, a.strip(), b.strip()).ratio() > 0.8 + +def chunk_by_tokens(text: str) -> List[str]: + segs = detect_code_blocks(clean_text_noise(text)) + chunks, buf, tok = [], [], 0 + + def flush(): + nonlocal buf, tok + if buf: + chunks.append(" ".join(buf).strip()) + buf = [] + tok = 0 + + for t, seg in segs: + if not seg.strip(): continue + if t == "code": + seg = seg.strip() + seg_toks = num_tokens(seg) + code_parts = [seg] if seg_toks <= MAX_TOKENS else split_huge_code_block(seg) + # dedupe similar adjacent code blocks (Lisp vs JS) + if buf and any(is_duplicate_code(seg, b) for b in buf if b.startswith("(") or b.startswith("function")): + continue + for part in code_parts: + part_toks = num_tokens(part) + if tok + part_toks > MAX_TOKENS: flush() + buf.append(part) + tok += part_toks + if tok >= MAX_TOKENS: flush() + continue + + for s in safe_sentence_tokenize(seg): + s = s.strip() + if not s: continue + stoks = num_tokens(s) + if stoks > MAX_TOKENS: + words = s.split() + for i in range(0,len(words),HARD_SENT_WORD_SPLIT): + sub = " ".join(words[i:i+HARD_SENT_WORD_SPLIT]) + if tok + num_tokens(sub) > MAX_TOKENS: flush() + buf.append(sub) + tok += num_tokens(sub) + if tok >= MAX_TOKENS: flush() + continue + if tok + stoks > MAX_TOKENS: flush() + buf.append(s) + tok += stoks + if buf: flush() + return [c for c in chunks if c.strip()] + +def make_chunk_key(e): return (e.get("title") or "", e.get("parent_title") or "", e.get("source_file") or "") + +# ---------- MAIN ---------- +def main(): + all_chunks = [] + files = sorted(glob(os.path.join(CHAPTER_JSON_DIR, CHAPTER_FILE_GLOB))) + print(f"📚 Processing {len(files)} chapter files...") + + for fpath in files: + with open(fpath, "r", encoding="utf-8") as f: + data = json.load(f) + + data = [d for d in data if isinstance(d, dict) and d.get("content")] + data.sort(key=lambda x: (x.get("parent_title") or "", x.get("title") or "", x.get("paragraph_index", 0))) + + for (title, parent, src), grp in groupby(data, key=make_chunk_key): + paras = [] + seen = set() + for p in grp: + txt = clean_text_noise(p["content"]) + if txt and txt not in seen: + seen.add(txt) + paras.append(txt) + merged = "\n".join(paras) + if not merged.strip(): continue + + subs = chunk_by_tokens(merged) + base = os.path.splitext(os.path.basename(fpath))[0] + safe_title = (title or "section").replace(" ", "_")[:60] + for i, ch in enumerate(subs, 1): + all_chunks.append({ + "chapter_file": os.path.basename(fpath), + "section": parent or None, + "subsection": title or None, + "chunk_id": f"{base}_{safe_title}_{i}", + "chunk_index": i, + "content": ch, + "token_count": num_tokens(ch), + "source_files": [src] + }) + + with open(OUTPUT_FILE, "w", encoding="utf-8") as out: + json.dump(all_chunks, out, indent=2, ensure_ascii=False) + + if all_chunks: + avg = sum(c["token_count"] for c in all_chunks)/len(all_chunks) + print(f"✅ Created {len(all_chunks)} chunks → {OUTPUT_FILE}") + print(f"📊 Avg tokens: {avg:.1f}, Max: {max(c['token_count'] for c in all_chunks)}") + else: + print("⚠️ No chunks produced.") + +if __name__ == "__main__": + main() diff --git a/parser/parse_sicp.py b/parser/parse_sicp.py new file mode 100644 index 000000000..a9d1b4915 --- /dev/null +++ b/parser/parse_sicp.py @@ -0,0 +1,116 @@ +import os +import re +import xml.etree.ElementTree as ET +import html +import json + +# Path to chapter folders +SICP_XML_DIR = os.path.join(os.path.dirname(__file__), "..", "xml") + +def parse_file(file_path, parent_title=None, depth=0): + """ + Recursively parse any XML file (chapter, section, or subsection). + """ + indent = " " * depth # for nice indentation in logs + + if not os.path.exists(file_path): + print(f"{indent}⚠️ Missing file: {file_path}") + return [] + + print(f"{indent}📄 Parsing ({depth=}): {file_path}") + + # Parse and unescape + try: + tree = ET.parse(file_path) + root = tree.getroot() + except Exception as e: + print(f"{indent}❌ XML parse error in {file_path}: {e}") + return [] + + xml_text = html.unescape(ET.tostring(root, encoding="unicode")) + chunks = [] + + # Identify tag type + tag_type = root.tag.upper() + if root.find("NAME") is not None: + title = " ".join(root.find("NAME").itertext()) + title = re.sub(r"\s+", " ", title).strip() + else: + title = "Untitled" + + # Extract text paragraphs + text_blocks = root.findall(".//TEXT") + print(f"{indent}🧩 Found {len(text_blocks)} blocks in {os.path.basename(file_path)}") + + for i, t in enumerate(text_blocks, start=1): + for bad_tag in ["INDEX", "LABEL", "CITATION", "FOOTNOTE", "COMMENT", "WEB_ONLY"]: + for el in t.findall(f".//{bad_tag}"): + el.clear() + + text_content = " ".join(t.itertext()).strip() + text_content = re.sub(r"\s+", " ", text_content) + + if text_content: + chunks.append({ + "source_file": os.path.basename(file_path), + "tag_type": tag_type, + "title": title, + "parent_title": parent_title, + "depth": depth, + "paragraph_index": i, + "content": text_content + }) + + # Look for section and subsection references + section_refs = re.findall(r"§ion([\d\.]+);", xml_text) + subsection_refs = re.findall(r"&subsection([\d\.]+);", xml_text) + + if section_refs: + print(f"{indent}🔍 Found {len(section_refs)} section ref(s): {section_refs}") + if subsection_refs: + print(f"{indent} ↳ Found {len(subsection_refs)} subsection ref(s): {subsection_refs}") + + # Recurse into sections + for ref in section_refs: + section_folder = os.path.join(os.path.dirname(file_path), f"section{ref.split('.')[0]}") + section_file = os.path.join(section_folder, f"section{ref.split('.')[0]}.xml") + print(f"{indent}➡️ Going into section file: {section_file}") + chunks.extend(parse_file(section_file, parent_title=title, depth=depth + 1)) + + # Recurse into subsections + for ref in subsection_refs: + subsection_file = os.path.join(os.path.dirname(file_path), f"subsection{ref.split('.')[0]}.xml") + print(f"{indent}➡️ Going into subsection file: {subsection_file}") + chunks.extend(parse_file(subsection_file, parent_title=title, depth=depth + 1)) + + print(f"{indent}✅ Done parsing {os.path.basename(file_path)}, total chunks so far: {len(chunks)}\n") + return chunks + +if __name__ == "__main__": + print("🚀 Starting full SICP parse\n") + + # ✅ Automatically detect all chapter folders (chapter1, chapter2, ...) + for chapter_dir in sorted(os.listdir(SICP_XML_DIR)): + if not chapter_dir.startswith("chapter"): + continue + + chapter_path = os.path.join(SICP_XML_DIR, chapter_dir, f"{chapter_dir}.xml") + if not os.path.exists(chapter_path): + print(f"⚠️ Skipping {chapter_dir}: main XML not found\n") + continue + + print(f"\n==============================") + print(f"📘 Parsing {chapter_dir}") + print(f"==============================") + + all_chunks = parse_file(chapter_path) + print(f"✅ Extracted {len(all_chunks)} chunks for {chapter_dir}\n") + + # Save separate JSON for each chapter + out_path = os.path.join(os.path.dirname(__file__), f"{chapter_dir}_chunks.json") + with open(out_path, "w", encoding="utf-8") as f: + json.dump(all_chunks, f, indent=2, ensure_ascii=False) + + print(f"💾 Saved {chapter_dir}_chunks.json ({len(all_chunks)} chunks)\n") + + print("🏁 All chapters processed successfully!") \ No newline at end of file diff --git a/parser/sicp_mesochunks_semantic_rag.json b/parser/sicp_mesochunks_semantic_rag.json new file mode 100644 index 000000000..b833905a7 --- /dev/null +++ b/parser/sicp_mesochunks_semantic_rag.json @@ -0,0 +1,1250 @@ +[ + { + "chapter_file": "chapter1_chunks.json", + "section": null, + "subsection": "Building Abstractions with Procedures Functions", + "chunk_id": "chapter1_chunks_Building_Abstractions_with_Procedures_Functions_1", + "chunk_index": 1, + "content": "We are about to study the idea of a computational process . data . program . People create programs to direct processes. In effect, we conjure the spirits of the computer with our spells. A computational process is indeed much like a sorcerer s idea of a spirit. It cannot be seen or touched. It is not composed of matter at all. However, it is very real. It can perform intellectual work. It can answer questions. It can affect the world by disbursing money at a bank or by controlling a robot arm in a factory. The programs we use to conjure processes are like a sorcerer s spells. They are carefully composed from symbolic expressions in arcane and esoteric programming languages\nA computational process, in a correctly working computer, executes programs precisely and accurately. Thus, like the sorcerer s apprentice, novice programmers must learn to understand and to anticipate the consequences of their conjuring. Even small errors (usually called bugs or glitches ) (usually called bugs ) in programs can have complex and unanticipated consequences. Fortunately, learning to program is considerably less dangerous than learning sorcery, because the spirits we deal with are conveniently contained in a secure way. Real-world programming, however, requires care, expertise, and wisdom. A small bug in a computer-aided design program, for example, can lead to the catastrophic collapse of an airplane or a dam or the self-destruction of an industrial robot.", + "token_count": 287, + "source_files": [ + "chapter1.xml" + ] + }, + { + "chapter_file": "chapter1_chunks.json", + "section": null, + "subsection": "Building Abstractions with Procedures Functions", + "chunk_id": "chapter1_chunks_Building_Abstractions_with_Procedures_Functions_2", + "chunk_index": 2, + "content": "Master software engineers have the ability to organize programs so that they can be reasonably sure that the resulting processes will perform the tasks intended. They can visualize the behavior of their systems in advance. They know how to structure programs so that unanticipated problems do not lead to catastrophic consequences, and when problems do arise, they can debug their programs. Well-designed computational systems, like well-designed automobiles or nuclear reactors, are designed in a modular manner, so that the parts can be constructed, replaced, and debugged separately. We need an appropriate language for describing processes, and we will use for this purpose the programming language Lisp. Just as our everyday thoughts are usually expressed in our natural language (such as English, Swedish, or German), and descriptions of quantitative phenomena are expressed with mathematical notations, our procedural thoughts will be expressed in Lisp. recursion equations , as a model for computation. The language was conceived by Recursive Functions of Symbolic Expressions and Their Computation by Machine (\nWe need an appropriate language for describing processes, and we will use for this purpose the programming language JavaScript. Just as our everyday thoughts are usually expressed in our natural language (such as English, Swedish, or Chinese), and descriptions of quantitative phenomena are expressed with mathematical notations, our procedural thoughts will be expressed in JavaScript. Mocha , which was later renamed to LiveScript , and finally to JavaScript. JavaScript is a trademark of Oracle Corporation. Despite its inception as a mathematical formalism, Lisp is a practical programming language.", + "token_count": 298, + "source_files": [ + "chapter1.xml" + ] + }, + { + "chapter_file": "chapter1_chunks.json", + "section": null, + "subsection": "Building Abstractions with Procedures Functions", + "chunk_id": "chapter1_chunks_Building_Abstractions_with_Procedures_Functions_3", + "chunk_index": 3, + "content": "A Lisp interpreter is a machine that carries out processes described in the Lisp language. The first Lisp interpreter was implemented by\nDespite its inception as a language for scripting the web, JavaScript interpreter is a machine that carries out processes described in the JavaScript language. Lisp was not the product of a concerted design effort. Instead, it evolved informally in an experimental manner in response to users needs and to pragmatic implementation considerations. Lisp s informal evolution has continued through the years, and the community of Lisp users has traditionally resisted attempts to promulgate any official definition of the language. This evolution, together with the flexibility and elegance of the initial conception, has enabled Lisp, which is the second oldest language in widespread use today (only\nJavaScript bears only superficial resemblance to the language Java, after which it was (eventually) named; both Java and JavaScript use the block structure of the language C. In contrast with Java and C, which usually employ compilation to lower-level languages, JavaScript programs were initially interpreted by web browsers. s Internet Explorer, whose JavaScript version is called JScript . The popularity of JavaScript for controlling web browsers gave rise to a standardization effort, culminating in ECMAScript . The and completed in June 1997 (\nBecause of its experimental character and its emphasis on symbol manipulation,\nThe practice of embedding JavaScript programs in web pages encouraged the developers of web browsers to implement JavaScript interpreters.", + "token_count": 279, + "source_files": [ + "chapter1.xml" + ] + }, + { + "chapter_file": "chapter1_chunks.json", + "section": null, + "subsection": "Building Abstractions with Procedures Functions", + "chunk_id": "chapter1_chunks_Building_Abstractions_with_Procedures_Functions_4", + "chunk_index": 4, + "content": "As these programs became more complex, the interpreters became more efficient in executing them, eventually using sophisticated implementation techniques such as Just-In-Time (JIT) compilation. The majority of JavaScript programs as of this writing (2021) are embedded in web pages and interpreted by browsers, but JavaScript is increasingly used as a general-purpose programming language, using systems such as Node.js. If Lisp is not a mainstream language, why are we using it as the framework for our discussion of programming? Because the language possesses procedures , can themselves be represented and manipulated as Lisp data. The importance of this is that there are powerful program-design techniques that rely on the ability to blur the traditional distinction between passive data and active processes. As we shall discover, Lisp s flexibility in handling procedures as data makes it one of the most convenient languages in existence for exploring these techniques. The ability to represent procedures as data also makes Lisp an excellent language for writing programs that must manipulate other programs as data, such as the interpreters and compilers that support computer languages. Above and beyond these considerations, programming in Lisp is great fun.", + "token_count": 219, + "source_files": [ + "chapter1.xml" + ] + }, + { + "chapter_file": "chapter1_chunks.json", + "section": "Building Abstractions with Procedures Functions", + "subsection": "The Elements of Programming", + "chunk_id": "chapter1_chunks_The_Elements_of_Programming_1", + "chunk_index": 1, + "content": "A powerful 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. Thus, 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: primitive expressions , cerned with, means of combination , by means of abstraction ,\nIn programming, we deal with two kinds of elements: and stuff that we want to manipulate, and procedures functions are descriptions of the rules for manipulating the data. Thus, any powerful programming language should be able to describe primitive data and primitive procedures functions and should have methods for combining and abstracting procedures functions and data. In this chapter we will deal only with simple procedures functions . procedures functions to manipulate compound data as well.", + "token_count": 177, + "source_files": [ + "section1.xml" + ] + }, + { + "chapter_file": "chapter1_chunks.json", + "section": "The Elements of Programming", + "subsection": "Expressions", + "chunk_id": "chapter1_chunks_Expressions_1", + "chunk_index": 1, + "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression. One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486\nExpressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7", + "token_count": 206, + "source_files": [ + "subsection1.xml" + ] + }, + { + "chapter_file": "chapter1_chunks.json", + "section": "The Elements of Programming", + "subsection": "Expressions", + "chunk_id": "chapter1_chunks_Expressions_2", + "chunk_index": 2, + "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7\nExpressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands. Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations . The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses.", + "token_count": 242, + "source_files": [ + "subsection1.xml" + ] + }, + { + "chapter_file": "chapter1_chunks.json", + "section": "The Elements of Programming", + "subsection": "Expressions", + "chunk_id": "chapter1_chunks_Expressions_3", + "chunk_index": 3, + "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19\nThe convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative . There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate.", + "token_count": 268, + "source_files": [ + "subsection1.xml" + ] + }, + { + "chapter_file": "chapter1_chunks.json", + "section": "The Elements of Programming", + "subsection": "Expressions", + "chunk_id": "chapter1_chunks_Expressions_4", + "chunk_index": 4, + "content": "It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting\nThere is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression. Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result.", + "token_count": 289, + "source_files": [ + "subsection1.xml" + ] + }, + { + "chapter_file": "chapter1_chunks.json", + "section": "The Elements of Programming", + "subsection": "Expressions", + "chunk_id": "chapter1_chunks_Expressions_5", + "chunk_index": 5, + "content": "This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement.", + "token_count": 51, + "source_files": [ + "subsection1.xml" + ] + }, + { + "chapter_file": "chapter2_chunks.json", + "section": null, + "subsection": "Building Abstractions with Data", + "chunk_id": "chapter2_chunks_Building_Abstractions_with_Data_1", + "chunk_index": 1, + "content": "We concentrated in chapter on computational processes and on the role of procedures functions in program design. We saw how to use primitive data (numbers) and primitive operations (arithmetic operations), how to combine procedures functions to form compound procedures functions through composition, conditionals, and the use of parameters, and how to abstract procedures processes by using define . function declarations. We saw that a procedure function can be regarded as a pattern for the local evolution of a process, and we classified, reasoned about, and performed simple algorithmic analyses of some common patterns for processes as embodied in procedures. functions. We also saw that higher-order procedures 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. In this chapter we are going to look at more complex data. All the procedures functions in chapter operate on simple numerical data, and simple data are not sufficient for many of the problems we wish to address using computation. Programs are typically designed to model complex phenomena, and more often than not one must construct computational objects that have several parts in order to model real-world phenomena that have several aspects. Thus, whereas our focus in chapter was on building abstractions by combining procedures functions to form compound procedures, functions, we turn in this chapter to another key aspect of any programming language: the means it provides for building abstractions by combining data objects to form compound data .", + "token_count": 291, + "source_files": [ + "chapter2.xml" + ] + }, + { + "chapter_file": "chapter2_chunks.json", + "section": null, + "subsection": "Building Abstractions with Data", + "chunk_id": "chapter2_chunks_Building_Abstractions_with_Data_2", + "chunk_index": 2, + "content": "Why do we want compound data in a programming language? For the same reasons that we want compound procedures: functions: to elevate the conceptual level at which we can design our programs, to increase the modularity of our designs, and to enhance the expressive power of our language. Just as the ability to define procedures declare functions enables us to deal with processes at a higher conceptual level than that of the primitive operations of the language, the ability to construct compound data objects enables us to deal with data at a higher conceptual level than that of the primitive data objects of the language. Consider the task of designing a system to perform add-rat add_rat that takes two rational numbers and produces their sum. In terms of simple data, a rational number can be thought of as two integers: a numerator and a denominator. Thus, we could design a program in which each rational number would be represented by two integers (a numerator and a denominator) and where add-rat add_rat would be implemented by two procedures functions (one producing the numerator of the sum and one producing the denominator). But this would be awkward, because we would then need to explicitly keep track of which numerators corresponded to which denominators. In a system intended to perform many operations on many rational numbers, such bookkeeping details would clutter the programs substantially, to say nothing of what they would do to our minds.", + "token_count": 275, + "source_files": [ + "chapter2.xml" + ] + }, + { + "chapter_file": "chapter2_chunks.json", + "section": null, + "subsection": "Building Abstractions with Data", + "chunk_id": "chapter2_chunks_Building_Abstractions_with_Data_3", + "chunk_index": 3, + "content": "It would be much better if we could glue together a numerator and denominator to form a pair a compound data object that our programs could manipulate in a way that would be consistent with regarding a rational number as a single conceptual unit. The use of compound data also enables us to increase the modularity of our programs. If we can manipulate rational numbers directly as objects in their own right, then we can separate the part of our program that deals with rational numbers per se from the details of how rational numbers may be represented as pairs of integers. The general technique of isolating the parts of a program that deal with how data objects are represented from the parts of a program that deal with how data objects are used is a powerful design methodology called data abstraction . We will see how data abstraction makes programs much easier to design, maintain, and modify. The use of compound data leads to a real increase in the expressive power of our programming language. Consider the idea of forming a linear combination $ax+by$ . We might like to write a procedure function that would accept $a$ , $b$ , $x$ , and $y$ as arguments and return the value of $ax+by$ .", + "token_count": 240, + "source_files": [ + "chapter2.xml" + ] + }, + { + "chapter_file": "chapter2_chunks.json", + "section": null, + "subsection": "Building Abstractions with Data", + "chunk_id": "chapter2_chunks_Building_Abstractions_with_Data_4", + "chunk_index": 4, + "content": "This presents no difficulty if the arguments are to be numbers, because we can readily define the procedure declare the function (define (linear-combination a b x y) (+ (* a x) (* b y))) function linear_combination(a, b, x, y) { return a * x + b * y; } linear_combination(1, 2, 3, 4); But suppose we are not concerned only with numbers. Suppose we would like to express, in procedural terms, the idea that one can form describe a process that forms linear combinations whenever addition and multiplication are defined for rational numbers, complex numbers, polynomials, or whatever. We could express this as a procedure function of the form (define (linear-combination a b x y) (add (mul a x) (mul b y))) function linear_combination(a, b, x, y) { return add(mul(a, x), mul(b, y)); } where add and mul are not the primitive procedures functions + and * but rather more complex things that will perform the appropriate operations for whatever kinds of data we pass in as the arguments a , b , x , and y . The key point is that the only thing linear-combination linear_combination should need to know about a , b , x , and y is that the procedures functions add and mul will perform the appropriate manipulations.", + "token_count": 285, + "source_files": [ + "chapter2.xml" + ] + }, + { + "chapter_file": "chapter2_chunks.json", + "section": null, + "subsection": "Building Abstractions with Data", + "chunk_id": "chapter2_chunks_Building_Abstractions_with_Data_5", + "chunk_index": 5, + "content": "From the perspective of the procedure function linear-combination , linear_combination , it is irrelevant what a , b , x , and y are and even more irrelevant how they might happen to be represented in terms of more primitive data. This same example shows why it is important that our programming language provide the ability to manipulate compound objects directly: Without this, there is no way for a procedure function such as linear-combination linear_combination to pass its arguments along to add and mul without having to know their detailed structure. We begin this chapter by implementing the rational-number arithmetic system mentioned above. This will form the background for our discussion of compound data and data abstraction. As with compound procedures, functions, the main issue to be addressed is that of abstraction as a technique for coping with complexity, and we will see how data abstraction enables us to erect suitable abstraction barriers between different parts of a program. We will see that the key to forming compound data is that a programming language should provide some kind of glue so that data objects can be combined to form more complex data objects. There are many possible kinds of glue. Indeed, we will discover how to form compound data using no special data operations at all, only procedures. functions. This will further blur the distinction between procedure function and data, which was already becoming tenuous toward the end of chapter . We will also explore some conventional techniques for representing sequences and trees.", + "token_count": 283, + "source_files": [ + "chapter2.xml" + ] + }, + { + "chapter_file": "chapter2_chunks.json", + "section": null, + "subsection": "Building Abstractions with Data", + "chunk_id": "chapter2_chunks_Building_Abstractions_with_Data_6", + "chunk_index": 6, + "content": "One key idea in dealing with compound data is the notion of closure that the glue we use for combining data objects should allow us to combine not only primitive data objects, but compound data objects as well. Another key idea is that compound data objects can serve as conventional interfaces for combining program modules in mix-and-match ways. We illustrate some of these ideas by presenting a simple graphics language that exploits closure. We will then augment the representational power of our language by introducing symbolic expressions data whose elementary parts can be arbitrary symbols rather than only numbers. We explore various alternatives for representing sets of objects. We will find that, just as a given numerical function can be computed by many different computational processes, there are many ways in which a given data structure can be represented in terms of simpler objects, and the choice of representation can have significant impact on the time and space requirements of processes that manipulate the data. We will investigate these ideas in the context of symbolic differentiation, the representation of sets, and the encoding of information. Next we will take up the problem of working with data that may be represented differently by different parts of a program. This leads to the need to implement generic operations , which must handle many different types of data. Maintaining modularity in the presence of generic operations requires more powerful abstraction barriers than can be erected with simple data abstraction alone.", + "token_count": 268, + "source_files": [ + "chapter2.xml" + ] + }, + { + "chapter_file": "chapter2_chunks.json", + "section": null, + "subsection": "Building Abstractions with Data", + "chunk_id": "chapter2_chunks_Building_Abstractions_with_Data_7", + "chunk_index": 7, + "content": "In particular, we introduce data-directed programming as a technique that allows individual data representations to be designed in isolation and then combined additively (i.e., without modification). To illustrate the power of this approach to system design, we close the chapter by applying what we have learned to the implementation of a package for performing symbolic arithmetic on polynomials, in which the coefficients of the polynomials can be integers, rational numbers, complex numbers, and even other polynomials.", + "token_count": 93, + "source_files": [ + "chapter2.xml" + ] + }, + { + "chapter_file": "chapter2_chunks.json", + "section": "Building Abstractions with Data", + "subsection": "Hierarchical Data and the Closure Property", + "chunk_id": "chapter2_chunks_Hierarchical_Data_and_the_Closure_Property_1", + "chunk_index": 1, + "content": "As we have seen, pairs provide a primitive glue that we can use to construct compound data objects. Figure Figure shows a standard way to visualize a in this case, the pair formed by (cons 1 2) . pair(1, 2) . In this representation, which is called box-and-pointer notation , each object is shown as a pointer to a box. The box for a primitive object contains a representation of the object. For example, the box for a number contains a numeral. The box for a pair is actually a double box, the left part containing (a pointer to) the car of the pair and the right part containing the cdr . In this representation, which is called box-and-pointer notation , each compound object is shown as a pointer to a box. The box for a pair has two parts, the left part containing the head of the pair and the right part containing the tail. We have already seen that cons pair can be used to combine not only numbers but pairs as well. (You made use of this fact, or should have, in doing exercises and .) As a consequence, pairs provide a universal building block from which we can construct all sorts of data structures. Figure Figure shows two ways to use pairs to combine the numbers 1, 2, 3, and 4. Two ways to combine 1, 2, 3, and 4 using pairs.", + "token_count": 287, + "source_files": [ + "section2.xml" + ] + }, + { + "chapter_file": "chapter2_chunks.json", + "section": "Building Abstractions with Data", + "subsection": "Hierarchical Data and the Closure Property", + "chunk_id": "chapter2_chunks_Hierarchical_Data_and_the_Closure_Property_2", + "chunk_index": 2, + "content": "Two ways to combine 1, 2, 3, and 4 using pairs. The ability to create pairs whose elements are pairs is the essence of list structure s importance as a representational tool. We refer to this ability as the closure property of cons . pair . In general, an operation for combining data objects satisfies the closure property if the results of combining things with that operation can themselves be combined using the same operation. hierarchical structures structures made up of parts, which themselves are made up of parts, and so on. From the outset of chapter , we ve made essential use of closure in dealing with procedures, functions, because all but the very simplest programs rely on the fact that the elements of a combination can themselves be combinations. In this section, we take up the consequences of closure for compound data. We describe some conventional techniques for using pairs to represent sequences and trees, and we exhibit a graphics language that illustrates closure in a vivid way.", + "token_count": 192, + "source_files": [ + "section2.xml" + ] + }, + { + "chapter_file": "chapter2_chunks.json", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Hierarchical Structures", + "chunk_id": "chapter2_chunks_Hierarchical_Structures_1", + "chunk_index": 1, + "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) .\nAnother way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree.", + "token_count": 259, + "source_files": [ + "subsection2.xml" + ] + }, + { + "chapter_file": "chapter2_chunks.json", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Hierarchical Structures", + "chunk_id": "chapter2_chunks_Hierarchical_Structures_2", + "chunk_index": 2, + "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8\nTo implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));", + "token_count": 627, + "source_files": [ + "subsection2.xml" + ] + }, + { + "chapter_file": "chapter2_chunks.json", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Hierarchical Structures", + "chunk_id": "chapter2_chunks_Hierarchical_Structures_3", + "chunk_index": 3, + "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))", + "token_count": 336, + "source_files": [ + "subsection2.xml" + ] + }, + { + "chapter_file": "chapter2_chunks.json", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Hierarchical Structures", + "chunk_id": "chapter2_chunks_Hierarchical_Structures_4", + "chunk_index": 4, + "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion.", + "token_count": 253, + "source_files": [ + "subsection2.xml" + ] + }, + { + "chapter_file": "chapter3_chunks.json", + "section": null, + "subsection": "Modularity, Objects, and State", + "chunk_id": "chapter3_chunks_Modularity,_Objects,_and_State_1", + "chunk_index": 1, + "content": "The preceding chapters introduced the basic elements from which programs are made. We saw how primitive procedures functions and primitive data are combined to construct compound entities, and we learned that abstraction is vital in helping us to cope with the complexity of large systems. But these tools are not sufficient for designing programs. Effective program synthesis also requires organizational principles that can guide us in formulating the overall design of a program. In particular, we need strategies to help us structure large systems so that they will be modular , that is, so that they can be divided naturally into coherent parts that can be separately developed and maintained. One powerful design strategy, which is particularly appropriate to the construction of programs for\nTo a large extent, then, the way we organize a large program is dictated by our perception of the system to be modeled. In this chapter we will investigate two prominent organizational strategies arising from two rather different world views of the structure of systems. The first organizational strategy concentrates on objects , viewing a large system as a collection of distinct objects whose behaviors may change over time. An alternative organizational strategy concentrates on the streams of information that flow in the system, much as an electrical engineer views a signal-processing system. Both the object-based approach and the stream-processing approach raise significant linguistic issues in programming. With objects, we must be concerned with how a computational object can change and yet maintain its identity.", + "token_count": 273, + "source_files": [ + "chapter3.xml" + ] + }, + { + "chapter_file": "chapter3_chunks.json", + "section": null, + "subsection": "Modularity, Objects, and State", + "chunk_id": "chapter3_chunks_Modularity,_Objects,_and_State_2", + "chunk_index": 2, + "content": "This will force us to abandon our old substitution model of computation (section ) in favor of a more mechanistic but less theoretically tractable environment model of computation. The difficulties of dealing with objects, change, and identity are a fundamental consequence of the need to grapple with time in our computational models. These difficulties become even greater when we allow the possibility of concurrent execution of programs. The stream approach can be most fully exploited when we decouple simulated time in our model from the order of the events that take place in the computer during evaluation. We will accomplish this using a technique known as delayed evaluation .", + "token_count": 120, + "source_files": [ + "chapter3.xml" + ] + }, + { + "chapter_file": "chapter3_chunks.json", + "section": "Modeling with Mutable Data", + "subsection": "Representing Tables", + "chunk_id": "chapter3_chunks_Representing_Tables_1", + "chunk_index": 1, + "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures. We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list. To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head .", + "token_count": 284, + "source_files": [ + "subsection3.xml" + ] + }, + { + "chapter_file": "chapter3_chunks.json", + "section": "Modeling with Mutable Data", + "subsection": "Representing Tables", + "chunk_id": "chapter3_chunks_Representing_Tables_2", + "chunk_index": 2, + "content": "Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }\nTo insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value.", + "token_count": 290, + "source_files": [ + "subsection3.xml" + ] + }, + { + "chapter_file": "chapter3_chunks.json", + "section": "Modeling with Mutable Data", + "subsection": "Representing Tables", + "chunk_id": "chapter3_chunks_Representing_Tables_3", + "chunk_index": 3, + "content": "The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }", + "token_count": 133, + "source_files": [ + "subsection3.xml" + ] + }, + { + "chapter_file": "chapter3_chunks.json", + "section": "Modeling with Mutable Data", + "subsection": "Representing Tables", + "chunk_id": "chapter3_chunks_Representing_Tables_4", + "chunk_index": 4, + "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }\nIn a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table.", + "token_count": 189, + "source_files": [ + "subsection3.xml" + ] + }, + { + "chapter_file": "chapter3_chunks.json", + "section": "Modeling with Mutable Data", + "subsection": "Representing Tables", + "chunk_id": "chapter3_chunks_Representing_Tables_5", + "chunk_index": 5, + "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }\nTo insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }", + "token_count": 556, + "source_files": [ + "subsection3.xml" + ] + }, + { + "chapter_file": "chapter3_chunks.json", + "section": "Modeling with Mutable Data", + "subsection": "Representing Tables", + "chunk_id": "chapter3_chunks_Representing_Tables_6", + "chunk_index": 6, + "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }", + "token_count": 567, + "source_files": [ + "subsection3.xml" + ] + }, + { + "chapter_file": "chapter3_chunks.json", + "section": "Modeling with Mutable Data", + "subsection": "Representing Tables", + "chunk_id": "chapter3_chunks_Representing_Tables_7", + "chunk_index": 7, + "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table .", + "token_count": 120, + "source_files": [ + "subsection3.xml" + ] + }, + { + "chapter_file": "chapter3_chunks.json", + "section": "Modularity, Objects, and State", + "subsection": "Modeling with Mutable Data", + "chunk_id": "chapter3_chunks_Modeling_with_Mutable_Data_1", + "chunk_index": 1, + "content": "Chapter dealt with compound data as a means for constructing computational objects that have several parts, in order to model real-world objects that have several aspects. In that chapter we introduced the discipline of data abstraction, according to which data structures are specified in terms of constructors, which create data objects, and selectors, which access the parts of compound data objects. But we now know that there is another aspect of data that chapter did not address. The desire to model systems composed of objects that have changing state leads us to the need to modify compound data objects, as well as to construct and select from them. In order to model compound objects with changing state, we will design data abstractions to include, in addition to selectors and constructors, operations called mutators , which modify data objects. For instance, modeling a banking system requires us to change account balances. Thus, a data structure for representing bank accounts might admit an operation (set-balance! $account$ $new$-$value$) set_balance( account , new-value ) that changes the balance of the designated account to the designated new value. Data objects for which mutators are defined are known as mutable data objects . Chapter introduced pairs as a general-purpose glue for synthesizing compound data. We begin this section by defining basic mutators for pairs, so that pairs can serve as building blocks for constructing mutable data objects.", + "token_count": 274, + "source_files": [ + "section3.xml" + ] + }, + { + "chapter_file": "chapter3_chunks.json", + "section": "Modularity, Objects, and State", + "subsection": "Modeling with Mutable Data", + "chunk_id": "chapter3_chunks_Modeling_with_Mutable_Data_2", + "chunk_index": 2, + "content": "These mutators greatly enhance the representational power of pairs, enabling us to build data structures other than the sequences and trees that we worked with in section . We also present some examples of simulations in which complex systems are modeled as collections of objects with local state.", + "token_count": 52, + "source_files": [ + "section3.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": null, + "subsection": "Metalinguistic Abstraction", + "chunk_id": "chapter4_chunks_Metalinguistic_Abstraction_1", + "chunk_index": 1, + "content": "In our study of program design, we have seen that expert programmers control the complexity of their designs with the same general techniques used by designers of all complex systems. They combine primitive elements to form compound objects, they abstract compound objects to form higher-level building blocks, and they preserve modularity by adopting appropriate large-scale views of system structure. In illustrating these techniques, we have used Lisp JavaScript as a language for describing processes and for constructing computational data objects and processes to model complex phenomena in the real world. However, as we confront increasingly complex problems, we will find that Lisp, JavaScript, or indeed any fixed programming language, is not sufficient for our needs. We must constantly turn to new languages in order to express our ideas more effectively. Establishing new languages is a powerful strategy for controlling complexity in engineering design; we can often enhance our ability to deal with a complex problem by adopting a new language that enables us to describe (and hence to think about) the problem in a different way, using primitives, means of combination, and means of abstraction that are particularly well suited to the problem at hand. Programming is endowed with a multitude of languages. There are physical languages, such as the procedure definition, function declaration, that are appropriate to the larger-scale organization of systems. Metalinguistic abstraction establishing plays an important role in all branches of engineering design.", + "token_count": 271, + "source_files": [ + "chapter4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": null, + "subsection": "Metalinguistic Abstraction", + "chunk_id": "chapter4_chunks_Metalinguistic_Abstraction_2", + "chunk_index": 2, + "content": "It is particularly important to computer programming, because in programming not only can we formulate new languages but we can also implement these languages by constructing evaluators. An evaluator (or interpreter ) for a programming language is a procedure function that, when applied to a statement or expression an expression of the language, performs the actions required to evaluate that statement or expression. It is no exaggeration to regard this as the most fundamental idea in programming: The evaluator, which determines the meaning of statements and expressions in a programming language, is just another program. 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. In fact, we can regard almost any program as the evaluator for some language. For instance, the polynomial manipulation system of section embodies the rules of polynomial arithmetic and implements them in terms of operations on list-structured data. If we augment this system with procedures functions to read and print polynomial expressions, we have the core of a special-purpose language for dealing with problems in symbolic mathematics. The digital-logic simulator of section and the constraint propagator of section are legitimate languages in their own right, each with its own primitives, means of combination, and means of abstraction. Seen from this perspective, the technology for coping with large-scale computer systems merges with the technology for building new computer languages, and\nWe now embark on a tour of the technology by which languages are established in terms of other languages.", + "token_count": 300, + "source_files": [ + "chapter4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": null, + "subsection": "Metalinguistic Abstraction", + "chunk_id": "chapter4_chunks_Metalinguistic_Abstraction_3", + "chunk_index": 3, + "content": "In this chapter we shall use Lisp JavaScript as a base, implementing evaluators as Lisp JavaScript procedures. functions. We will take the first step in understanding how languages are implemented by building an evaluator for Lisp JavaScript itself. The language implemented by our evaluator will be a subset of the Scheme dialect of Lisp that we use in this book. JavaScript. Although the evaluator described in this chapter is written for a particular dialect of Lisp, subset of JavaScript, it contains the essential structure of an evaluator for any expression-oriented language designed for writing programs for a sequential machine. (In fact, most language processors contain, deep within them, a little Lisp evaluator.) The evaluator has been simplified for the purposes of illustration and discussion, and some features have been left out that would be important to include in a production-quality Lisp JavaScript system. Nevertheless, this simple evaluator is adequate to execute most of the programs in this book. An important advantage of making the evaluator accessible as a Lisp JavaScript program is that we can implement alternative evaluation rules by describing these as modifications to the evaluator program. One place where we can use this power to good effect is to gain extra control over the ways in which computational models embody the notion of time, which was so central to the discussion in chapter . There, we mitigated some of the complexities of state and assignment by using streams to decouple the representation of time in the world from time in the computer.", + "token_count": 281, + "source_files": [ + "chapter4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": null, + "subsection": "Metalinguistic Abstraction", + "chunk_id": "chapter4_chunks_Metalinguistic_Abstraction_4", + "chunk_index": 4, + "content": "Our stream programs, however, were sometimes cumbersome, because they were constrained by the applicative-order evaluation of Scheme. JavaScript. In section , we ll change the underlying language to provide for a more elegant approach, by modifying the evaluator to provide for normal-order evaluation . Section implements a more ambitious linguistic change, whereby statements and expressions have many values, rather than just a single value. In this language of nondeterministic computing , it is natural to express processes that generate all possible values for statements and expressions and then search for those values that satisfy certain constraints. In terms of models of computation and time, this is like having time branch into a set of possible futures and then searching for appropriate time lines. With our nondeterministic evaluator, keeping track of multiple values and performing searches are handled automatically by the underlying mechanism of the language. In section we implement a logic-programming language in which knowledge is expressed in terms of relations, rather than in terms of computations with inputs and outputs. Even though this makes the language drastically different from Lisp, JavaScript, or indeed from any conventional language, we will see that the logic-programming evaluator shares the essential structure of the Lisp JavaScript evaluator.", + "token_count": 235, + "source_files": [ + "chapter4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_1", + "chunk_index": 1, + "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system.", + "token_count": 23, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_2", + "chunk_index": 2, + "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section .", + "token_count": 1175, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_3", + "chunk_index": 3, + "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section ). To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section . The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query .", + "token_count": 287, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_4", + "chunk_index": 4, + "content": "evaluate_query operation_table_from_chapter_3 operation_table simple_query type (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms. The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query.", + "token_count": 183, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_5", + "chunk_index": 5, + "content": "simple_query stream_flatmap find_assertions apply_rules (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }\nFor each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern. And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames.", + "token_count": 297, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_6", + "chunk_index": 6, + "content": "First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered. Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .)", + "token_count": 278, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_7", + "chunk_index": 7, + "content": "disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);\nThe predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section . Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null?", + "token_count": 283, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_8", + "chunk_index": 8, + "content": "(qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);\nLisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation.", + "token_count": 225, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_9", + "chunk_index": 9, + "content": "compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);\nExecute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system.", + "token_count": 265, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_10", + "chunk_index": 10, + "content": "execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))\nThe always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section . Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame.", + "token_count": 290, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_11", + "chunk_index": 11, + "content": "The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }\nCheck-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq?", + "token_count": 272, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_12", + "chunk_index": 12, + "content": "match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var?", + "token_count": 294, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_13", + "chunk_index": 13, + "content": "pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }\nHere is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame.", + "token_count": 299, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_14", + "chunk_index": 14, + "content": "If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) .", + "token_count": 285, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_15", + "chunk_index": 15, + "content": "We never modify a stored binding and we never store more than one binding for a given variable. The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section . If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way. When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) . Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list.", + "token_count": 281, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_16", + "chunk_index": 16, + "content": "For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) . Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }\nApply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame. Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other.", + "token_count": 296, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_17", + "chunk_index": 17, + "content": "For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section .", + "token_count": 291, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_18", + "chunk_index": 18, + "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }\nThe procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }", + "token_count": 606, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_19", + "chunk_index": 19, + "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other.", + "token_count": 150, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_20", + "chunk_index": 20, + "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }", + "token_count": 595, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_21", + "chunk_index": 21, + "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees", + "token_count": 31, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_22", + "chunk_index": 22, + "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }\nOne important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }", + "token_count": 794, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_23", + "chunk_index": 23, + "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '?", + "token_count": 269, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_24", + "chunk_index": 24, + "content": "'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }", + "token_count": 41, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_25", + "chunk_index": 25, + "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }\nTo actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }", + "token_count": 528, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_26", + "chunk_index": 26, + "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index (define (use-index? pat) (constant-symbol? (car pat))) The query system uses a few stream operations that were not presented in chapter .", + "token_count": 224, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_27", + "chunk_index": 27, + "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }\nStream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }", + "token_count": 465, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_28", + "chunk_index": 28, + "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }\nWe saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation.", + "token_count": 278, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_29", + "chunk_index": 29, + "content": "Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system. The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;\nUnique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied.", + "token_count": 239, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_30", + "chunk_index": 30, + "content": "is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }\nThe function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ?", + "token_count": 287, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_31", + "chunk_index": 31, + "content": "processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }\nAn exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 .", + "token_count": 289, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_32", + "chunk_index": 32, + "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ .", + "token_count": 281, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_33", + "chunk_index": 33, + "content": "express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ?", + "token_count": 158, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_34", + "chunk_index": 34, + "content": "make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ .", + "token_count": 228, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_35", + "chunk_index": 35, + "content": "Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'\nThe function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise .", + "token_count": 272, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_36", + "chunk_index": 36, + "content": "A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ?", + "token_count": 286, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_37", + "chunk_index": 37, + "content": "symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists.", + "token_count": 199, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_38", + "chunk_index": 38, + "content": "In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ?", + "token_count": 299, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_39", + "chunk_index": 39, + "content": "null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }\nThe functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ?", + "token_count": 227, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_40", + "chunk_index": 40, + "content": "tail(exp) : error(exp, \"unknown expression contents\"); }\nThe following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }", + "token_count": 99, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_41", + "chunk_index": 41, + "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }\nThe following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }\nType and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))", + "token_count": 345, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_42", + "chunk_index": 42, + "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))", + "token_count": 83, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_43", + "chunk_index": 43, + "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))\nThe following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))\nQuery-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))", + "token_count": 486, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_44", + "chunk_index": 44, + "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))\nUnique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))", + "token_count": 177, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_id": "chapter4_chunks_Implementing_the_Query_System_45", + "chunk_index": 45, + "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))\nFrames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }", + "token_count": 257, + "source_files": [ + "subsection4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Metalinguistic Abstraction", + "subsection": "Logic Programming", + "chunk_id": "chapter4_chunks_Logic_Programming_1", + "chunk_index": 1, + "content": "In chapter we stressed that computer science deals with\nMost programming languages, including Lisp, JavaScript, are organized around computing the values of mathematical functions. Expression-oriented languages (such as Lisp, Fortran, Algol and JavaScript) (such as Lisp, C, Python, and JavaScript) capitalize on the pun that an expression that describes the value of a function may also be interpreted as a means of computing that value. Because of this, most programming languages are strongly biased toward unidirectional computations (computations with well-defined inputs and outputs). There are, however, radically different programming languages that relax this bias. We saw one such example in section , where the objects of computation were arithmetic constraints. In a constraint system the direction and the order of computation are not so well specified; in carrying out a computation the system must therefore provide more detailed how to knowledge than would be the case with an ordinary arithmetic computation. This does not mean, however, that the user is released altogether from the responsibility of providing imperative knowledge. There are many constraint networks that implement the same set of constraints, and the user must choose from the set of mathematically equivalent networks a suitable network to specify a particular computation. The nondeterministic program evaluator of section also moves away from the view that programming is about constructing algorithms for computing unidirectional functions. In a nondeterministic language, expressions can have more than one value, and, as a result, the computation is dealing with unification .", + "token_count": 295, + "source_files": [ + "section4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Metalinguistic Abstraction", + "subsection": "Logic Programming", + "chunk_id": "chapter4_chunks_Logic_Programming_2", + "chunk_index": 2, + "content": "This approach, when it works, can be a very what is fact can be used to solve a number of different problems that would have different how to components. As an example, consider the append operation, which takes two lists as arguments and combines their elements to form a single list. In a procedural language such as Lisp, JavaScript, we could define append in terms of the basic list constructor cons , pair , as we did in section : (define (append x y) (if (null? x) y (cons (car x) (append (cdr x) y)))) function append(x, y) { return is_null(x) ? y : pair(head(x), append(tail(x), y)); } This procedure function can be regarded as a translation into Lisp JavaScript of the following two rules, the first of which covers the case where the first list is empty and the second of which handles the case of a nonempty list, which is a cons pair of two parts: For any list y , the empty list and y append to form y . For any u , v , y , and z , (cons u v) pair(u, v) and y append to form (cons u z) pair(u, z) if v and y append to form z . Using the append procedure, function, we can answer questions such as Find the append of (a b) list(\"a\", \"b\") and (c d) . list(\"c\", \"d\") .", + "token_count": 300, + "source_files": [ + "section4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Metalinguistic Abstraction", + "subsection": "Logic Programming", + "chunk_id": "chapter4_chunks_Logic_Programming_3", + "chunk_index": 3, + "content": "But the same two rules are also sufficient for answering the following sorts of questions, which the procedure function can t answer: Find a list y that append s with (a b) list(\"a\", \"b\") to produce (a b c d) . list(\"a\", \"b\", \"c\", \"d\") . Find all x and y that append to form (a b c d) . list(\"a\", \"b\", \"c\", \"d\") . In a append procedure function by stating the two rules about append given above. How to knowledge is provided automatically by the interpreter to allow this single pair of rules to be used to answer all three types of questions about append . Contemporary logic programming languages (including the one we implement here) have substantial deficiencies, in that their general how to methods can lead them into spurious infinite loops or other undesirable behavior. Logic programming is an active field of research in computer science. Earlier in this chapter we explored the technology of implementing interpreters and described the elements that are essential to an interpreter for a Lisp-like JavaScript-like language (indeed, to an interpreter for any conventional language). Now we will apply these ideas to discuss an interpreter for a logic programming language. We call this language the query language , because it is very useful for retrieving information from data bases by formulating queries , or questions, expressed in the language.", + "token_count": 277, + "source_files": [ + "section4.xml" + ] + }, + { + "chapter_file": "chapter4_chunks.json", + "section": "Metalinguistic Abstraction", + "subsection": "Logic Programming", + "chunk_id": "chapter4_chunks_Logic_Programming_4", + "chunk_index": 4, + "content": "Even though the query language is very different from Lisp, JavaScript, we will find it convenient to describe the language in terms of the same general framework we have been using all along: as a collection of primitive elements, together with means of combination that enable us to combine simple elements to create more complex elements and means of abstraction that enable us to regard complex elements as single conceptual units. An interpreter for a logic programming language is considerably more complex than an interpreter for a language like Lisp. JavaScript. Nevertheless, we will see that our . In particular, there will be an evaluate part that classifies expressions according to type and an apply part that implements the language s abstraction mechanism (procedures (functions in the case of Lisp, JavaScript, and rules in the case of logic programming). Also, a central role is played in the implementation by a frame data structure, which determines the correspondence between symbols and their associated values. One additional interesting aspect of our query-language implementation is that we make substantial use of streams, which were introduced in chapter .", + "token_count": 206, + "source_files": [ + "section4.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": null, + "subsection": "Computing with Register Machines", + "chunk_id": "chapter5_chunks_Computing_with_Register_Machines_1", + "chunk_index": 1, + "content": "We began this book by studying processes and by describing processes in terms of procedures functions written in Lisp. JavaScript. To explain the meanings of these procedures, functions, we used a succession of models of evaluation: the substitution model of chapter , the environment model of chapter , and the metacircular evaluator of chapter . Our examination of the metacircular evaluator, in particular, dispelled much of the mystery of how Lisp-like languages are interpreted. JavaScript-like languages are interpreted. But even the metacircular evaluator leaves important questions unanswered, because it fails to elucidate the mechanisms of control in a Lisp JavaScript system. For instance, the evaluator does not explain how the evaluation of a subexpression manages to return a value to the expression that uses this value , nor does the evaluator explain how some recursive procedures generate iterative processes (that is, are evaluated using constant space) whereas other recursive procedures generate recursive processes . These questions remain unanswered because the metacircular evaluator is itself a Lisp program and hence inherits the control structure of the underlying Lisp system. In order to provide a more complete description of the control structure of the Lisp evaluator, we must work at a more primitive level than Lisp itself. Also, the evaluator does not explain how some recursive functions can generate iterative processes (that is, be evaluated using constant space) whereas other recursive functions will generate recursive processes. In this chapter we We will describe processes in terms of the step-by-step operation of a traditional computer.", + "token_count": 291, + "source_files": [ + "chapter5.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": null, + "subsection": "Computing with Register Machines", + "chunk_id": "chapter5_chunks_Computing_with_Register_Machines_2", + "chunk_index": 2, + "content": "Such a computer, or register machine , sequentially executes instructions that manipulate the contents of a fixed set of storage elements called registers . A typical register-machine instruction applies a primitive operation to the contents of some registers and assigns the result to another register. Our descriptions of processes executed by register machines will look very much like machine-language programs for traditional computers. However, instead of focusing on the machine language of any particular computer, we will examine several Lisp JavaScript procedures functions and design a specific register machine to execute each procedure. function. Thus, we will approach our task from the perspective of a hardware architect rather than that of a machine-language computer programmer. In designing register machines, we will develop mechanisms for implementing important programming constructs such as recursion. We will also present a language for describing designs for register machines. In section we will implement a Lisp JavaScript program that uses these descriptions to simulate the machines we design. Most of the primitive operations of our register machines are very simple. For example, an operation might add the numbers fetched from two registers, producing a result to be stored into a third register. Such an operation can be performed by easily described hardware. In order to deal with list structure, however, we will also use the memory operations car , head , cdr , tail , and cons , pair , which require an elaborate storage-allocation mechanism. In section we study their implementation in terms of more elementary operations.", + "token_count": 279, + "source_files": [ + "chapter5.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": null, + "subsection": "Computing with Register Machines", + "chunk_id": "chapter5_chunks_Computing_with_Register_Machines_3", + "chunk_index": 3, + "content": "In section , after we have accumulated experience formulating simple procedures functions as register machines, we will design a machine that carries out the algorithm described by the metacircular evaluator of section . This will fill in the gap in our understanding of how Scheme expressions JavaScript programs are interpreted, by providing an explicit model for the mechanisms of control in the evaluator. In section we will study a simple compiler that translates Scheme JavaScript programs into sequences of instructions that can be executed directly with the registers and operations of the evaluator register machine.", + "token_count": 102, + "source_files": [ + "chapter5.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": "Compilation", + "subsection": "An Example of Compiled Code", + "chunk_id": "chapter5_chunks_An_Example_of_Compiled_Code_1", + "chunk_index": 1, + "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary. Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration .", + "token_count": 250, + "source_files": [ + "subsection5.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": "Compilation", + "subsection": "An Example of Compiled Code", + "chunk_id": "chapter5_chunks_An_Example_of_Compiled_Code_2", + "chunk_index": 2, + "content": "This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))\nThe expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials.", + "token_count": 286, + "source_files": [ + "subsection5.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": "Compilation", + "subsection": "An Example of Compiled Code", + "chunk_id": "chapter5_chunks_An_Example_of_Compiled_Code_3", + "chunk_index": 3, + "content": "Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!)", + "token_count": 269, + "source_files": [ + "subsection5.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": "Compilation", + "subsection": "An Example of Compiled Code", + "chunk_id": "chapter5_chunks_An_Example_of_Compiled_Code_4", + "chunk_index": 4, + "content": "(const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))\nA procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function.", + "token_count": 275, + "source_files": [ + "subsection5.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": "Compilation", + "subsection": "An Example of Compiled Code", + "chunk_id": "chapter5_chunks_An_Example_of_Compiled_Code_5", + "chunk_index": 5, + "content": "Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?)", + "token_count": 220, + "source_files": [ + "subsection5.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": "Compilation", + "subsection": "An Example of Compiled Code", + "chunk_id": "chapter5_chunks_An_Example_of_Compiled_Code_6", + "chunk_index": 6, + "content": "(reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\",", + "token_count": 174, + "source_files": [ + "subsection5.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": "Compilation", + "subsection": "An Example of Compiled Code", + "chunk_id": "chapter5_chunks_An_Example_of_Compiled_Code_7", + "chunk_index": 7, + "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\",", + "token_count": 526, + "source_files": [ + "subsection5.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": "Compilation", + "subsection": "An Example of Compiled Code", + "chunk_id": "chapter5_chunks_An_Example_of_Compiled_Code_8", + "chunk_index": 8, + "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches.", + "token_count": 198, + "source_files": [ + "subsection5.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": "Computing with Register Machines", + "subsection": "Compilation", + "chunk_id": "chapter5_chunks_Compilation_1", + "chunk_index": 1, + "content": "The explicit-control evaluator of section is a register machine whose controller interprets Scheme JavaScript programs. In this section we will see how to run Scheme JavaScript programs on a register machine whose controller is not a Scheme JavaScript interpreter. The explicit-control evaluator machine is it can carry out any computational process that can be described in Scheme. JavaScript. The evaluator s controller orchestrates the use of its data paths to perform the desired computation. Thus, the evaluator s data paths are universal: They are sufficient to perform any computation we desire, given an appropriate controller. Commercial native language of the machine, or simply machine language . Programs written in machine language are sequences of instructions that use the machine s data paths. For example, the s instruction sequence can be thought of as a machine-language program for a general-purpose computer rather than as the controller for a specialized interpreter machine. There are two common strategies for bridging the gap between higher-level languages and register-machine languages. The explicit-control evaluator illustrates the strategy of interpretation. An interpreter written in the native language of a machine configures the machine to execute programs written in a language (called the source language ) that may differ from the native language of the machine performing the evaluation. The primitive procedures functions of the source language are implemented as a library of subroutines written in the native language of the given machine. A program to be interpreted (called the source program ) is represented as a data structure. The interpreter traverses this data structure, analyzing the source program.", + "token_count": 297, + "source_files": [ + "section5.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": "Computing with Register Machines", + "subsection": "Compilation", + "chunk_id": "chapter5_chunks_Compilation_2", + "chunk_index": 2, + "content": "As it does so, it simulates the intended behavior of the source program by calling appropriate primitive subroutines from the library. In this section, we explore the alternative strategy of compilation . A compiler for a given source language and machine translates a source program into an equivalent program (called the object program ) written in the machine s native language. The compiler that we implement in this section translates programs written in Scheme JavaScript into sequences of instructions to be executed using the explicit-control evaluator machine s data paths. Compared with interpretation, compilation can provide a great increase in the efficiency of program execution, as we will explain below in the overview of the compiler. On the other hand, an interpreter provides a more powerful environment for interactive program development and debugging, because the source program being executed is available at run time to be examined and modified. In addition, because the entire library of primitives is present, new programs can be constructed and added to the system during debugging. In view of the complementary advantages of compilation and interpretation, modern program-development environments pursue a mixed strategy. Lisp interpreters These systems are generally organized so that interpreted procedures functions and compiled procedures functions can call each other. This enables a programmer to compile those parts of a program that are assumed to be debugged, thus gaining the efficiency advantage of compilation, while retaining the interpretive mode of execution for those parts of the program that are in the flux of interactive development and debugging.", + "token_count": 284, + "source_files": [ + "section5.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": "Computing with Register Machines", + "subsection": "Compilation", + "chunk_id": "chapter5_chunks_Compilation_3", + "chunk_index": 3, + "content": "In section , after we have implemented the compiler, we will show how to interface it with our interpreter to produce an integrated interpreter-compiler development system. Our compiler is much like our interpreter, both in its structure and in the function it performs. Accordingly, the mechanisms used by the compiler for analyzing expressions components will be similar to those used by the interpreter. Moreover, to make it easy to interface compiled and interpreted code, we will design the compiler to generate code that obeys the same conventions of env register, argument lists will be accumulated in argl , a procedure function to be applied will be in proc , fun , procedures functions will return their answers in val , and the location to which a procedure function should return will be kept in continue . In general, the compiler translates a source program into an object program that performs essentially the same register operations as would the interpreter in evaluating the same source program. This description suggests a strategy for implementing a rudimentary compiler: We traverse the expression component in the same way the interpreter does. When we encounter a register instruction that the interpreter would perform in evaluating the expression, component, we do not execute the instruction but instead accumulate it into a sequence. The resulting sequence of instructions will be the object code.", + "token_count": 248, + "source_files": [ + "section5.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": "Computing with Register Machines", + "subsection": "Compilation", + "chunk_id": "chapter5_chunks_Compilation_4", + "chunk_index": 4, + "content": "Observe the an expression for example, a component for example, (f 84 96) it f(96, 22) it performs the work of classifying the expression component (discovering that this is a procedure function application) and testing for the end of the operand list (discovering that there are two operands). list of argument expressions (discovering that there are two argument expressions). With a compiler, the expression component is analyzed only once, when the instruction sequence is generated at compile time. The object code produced by the compiler contains only the instructions that evaluate the operator and the two operands, function expression and the two argument expressions, assemble the argument list, and apply the procedure (in proc ) function (in fun ) to the arguments (in argl ). This is the same kind of optimization we implemented in the . But there are further opportunities to gain efficiency in compiled code. As the interpreter runs, it follows a process that must be applicable to any expression component in the language. In contrast, a given segment of compiled code is meant to execute some particular expression. component. This can make a big difference, for example in the use of the stack to save registers. When the interpreter evaluates an expression, a component, it must be prepared for any contingency. Before evaluating a subexpression, subcomponent, the interpreter saves all registers that will be needed later, because the subexpression subcomponent might require an arbitrary evaluation.", + "token_count": 290, + "source_files": [ + "section5.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": "Computing with Register Machines", + "subsection": "Compilation", + "chunk_id": "chapter5_chunks_Compilation_5", + "chunk_index": 5, + "content": "A compiler, on the other hand, can exploit the structure of the particular expression component it is processing to generate code that avoids unnecessary stack operations. As a case in point, consider the combination (f 84 96) . application f(96, 22) . Before the interpreter evaluates the operator of the combination, function expression of the application, it prepares for this evaluation by saving the registers containing the operands argument expressions and the environment, whose values will be needed later. The interpreter then evaluates the operator function expression to obtain the result in val , restores the saved registers, and finally moves the result from val to proc . fun . However, in the particular expression we are dealing with, the operator function expression is the symbol name f , whose evaluation is accomplished by the machine operation lookup-variable-value , lookup_symbol_value , which does not alter any registers. The compiler that we implement in this section will take advantage of this fact and generate code that evaluates the operator function expression using the instruction (assign proc (op lookup-variable-value) (const f) (reg env)) assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"f\"), reg(\"env\"))) where the argument to lookup_symbol_value is extracted at compile time from the parser's representation of f(96, 22) . This code not only avoids the unnecessary saves and restores but also assigns the value of the lookup directly to proc , fun , whereas the interpreter would obtain the result in val and then move this to proc .", + "token_count": 300, + "source_files": [ + "section5.xml" + ] + }, + { + "chapter_file": "chapter5_chunks.json", + "section": "Computing with Register Machines", + "subsection": "Compilation", + "chunk_id": "chapter5_chunks_Compilation_6", + "chunk_index": 6, + "content": "fun . A compiler can also optimize access to the environment. Having analyzed the code, the compiler can in many cases know in which frame a particular variable the value of a particular name will be located and access that frame directly, rather than performing the lookup-variable-value lookup_@symbol_@value search. We will discuss how to implement such variable access lexical addressing in section . Until then, however, we will focus on the kind of register and stack optimizations described above. There are many other optimizations that can be performed by a compiler, such as coding primitive operations in line instead of using a general apply mechanism (see exercise ); but we will not emphasize these here. Our main goal in this section is to illustrate the compilation process in a simplified (but still interesting) context.", + "token_count": 155, + "source_files": [ + "section5.xml" + ] + } +] \ No newline at end of file From 4cec811521d991e23e40f08efc9fdedfd197c771 Mon Sep 17 00:00:00 2001 From: Isha Sovasaria Date: Thu, 6 Nov 2025 19:05:50 +0800 Subject: [PATCH 2/3] updated chunking logic --- parser/chunking.py | 617 ++- parser/sicp_mesochunks_semantic_rag.json | 4808 +++++++++++++++++----- 2 files changed, 4271 insertions(+), 1154 deletions(-) diff --git a/parser/chunking.py b/parser/chunking.py index 663affc27..48347a4d1 100644 --- a/parser/chunking.py +++ b/parser/chunking.py @@ -1,10 +1,11 @@ +import html +import json import os import re -import json +import xml.etree.ElementTree as ET from glob import glob from itertools import groupby -from difflib import SequenceMatcher -from typing import List, Tuple +from typing import Dict, Iterable, List, Optional try: import nltk @@ -25,17 +26,28 @@ _ENC = None # ---------- CONFIG ---------- -CHAPTER_JSON_DIR = "." -CHAPTER_FILE_GLOB = "chapter*_chunks.json" -OUTPUT_FILE = "sicp_mesochunks_semantic_rag.json" +BASE_DIR = os.path.dirname(__file__) +SICP_XML_DIR = os.path.join(BASE_DIR, "..", "xml") +OUTPUT_FILE = os.path.join(BASE_DIR, "sicp_mesochunks_semantic_rag.json") -MAX_TOKENS = 300 # smaller chunks for RAG +MAX_TOKENS = 300 OVERLAP_SENTENCES = 1 -HARD_SENT_WORD_SPLIT = 80 -HUGE_CODE_LINES = 25 -SYMBOL_DENSITY = 0.10 -# ---------- HELPERS ---------- +# ---------- PARSING HELPERS ---------- +DROP_TAGS = { + "INDEX", + "LABEL", + "CITATION", + "COMMENT", + "FOOTNOTE", + "WEB_ONLY", + "HISTORY", +} +SCHEME_PREFIXES = ("SCHEME",) +CODE_BLOCK_TAGS = {"SNIPPET", "PROGRAMLISTING", "CODE", "DISPLAY"} +INLINE_CODE_TAGS = {"JAVASCRIPTINLINE"} + + def num_tokens(text: str) -> int: if _ENC: try: @@ -44,195 +56,448 @@ def num_tokens(text: str) -> int: pass return max(1, len(text.split())) + def safe_sentence_tokenize(text: str) -> List[str]: if _NLTK_OK and nltk: try: return nltk.sent_tokenize(text) except Exception: pass - parts = re.split(r'(?<=[.!?])\s+(?=[A-Z(0-9`])', text.strip()) + parts = re.split(r"(?<=[.!?])\s+(?=[A-Z(0-9`])", text.strip()) if len(parts) == 1: words = text.split() - return [" ".join(words[i:i+HARD_SENT_WORD_SPLIT]) for i in range(0,len(words),HARD_SENT_WORD_SPLIT)] or [text] + chunk = [] + acc: List[str] = [] + for word in words: + acc.append(word) + if len(acc) >= 25: + chunk.append(" ".join(acc)) + acc = [] + if acc: + chunk.append(" ".join(acc)) + return chunk or [text] return parts -def is_symbol_dense(line: str) -> bool: - symbols = re.findall(r"[()\[\]{};:+\-*/=<>|&^%$~,.`]", line) - return (len(symbols) / max(1, len(line))) >= SYMBOL_DENSITY - -def looks_like_code_line(line: str) -> bool: - s = line.strip() - if not s: return False - if s == "```": return True - if s.startswith((" ", "\t")): return True - if re.match(r"^\s*(>>>|#|//|/\*|\*|\w+\s*=\s*)", s): return True - if re.match(r"^\s*\(.*\)\s*$", s): return True # Lisp - if re.match(r"^\s*(function\s+\w+\s*\(|\w+\s*\(.*\)\s*;?\s*|\{\s*|\}\s*)$", s): return True - if re.match(r"^\s*[\d.]+\s*[\+\-\*/]\s*[\d.]+;?\s*$", s): return True - if is_symbol_dense(s): return True - return False - -def detect_code_blocks(text: str) -> List[Tuple[str,str]]: - segs, cur, is_code, fenced = [], [], False, False - lines = text.splitlines() - for i, raw in enumerate(lines): - line = raw.rstrip("\n") - if line.strip() == "```": - if cur: - segs.append(("code" if is_code or fenced else "text", "\n".join(cur))) - cur = [] - fenced = not fenced - is_code = fenced or is_code + +def normalize_whitespace(text: str) -> str: + text = re.sub(r"[ \t]+", " ", text) + text = re.sub(r"\s*\n\s*", "\n", text) + text = re.sub(r"\n{3,}", "\n\n", text) + return text.strip() + + +def normalize_code(text: str) -> str: + lines = [line.rstrip() for line in text.splitlines()] + while lines and not lines[0].strip(): + lines.pop(0) + while lines and not lines[-1].strip(): + lines.pop() + return "\n".join(lines) + + +def prune_tree(node: ET.Element) -> None: + for child in list(node): + tag = child.tag.upper() + if tag in DROP_TAGS or tag.startswith(SCHEME_PREFIXES): + node.remove(child) continue + prune_tree(child) - # decide next_line for boundary detection - next_line = lines[i+1].strip() if i+1 < len(lines) else "" - if fenced or looks_like_code_line(line): - if not is_code and cur: - segs.append(("text", "\n".join(cur))) - cur = [] - is_code = True - else: - # boundary: code followed by capitalized prose - if is_code and next_line and re.match(r"^[A-Z]", next_line): - segs.append(("code", "\n".join(cur+[line]))) - cur, is_code = [], False - continue - if is_code and cur: - segs.append(("code", "\n".join(cur))) - cur = [] - is_code = False - cur.append(line) - if cur: - segs.append(("code" if is_code or fenced else "text", "\n".join(cur))) - return segs - -def split_code_by_lines(block: str, window: int) -> List[str]: - lines, out, cur = block.split("\n"), [], [] - for ln in lines: - cur.append(ln) - if len(cur) >= window or num_tokens("\n".join(cur)) >= MAX_TOKENS: - out.append("\n".join(cur)) - cur = [] - if cur: out.append("\n".join(cur)) - return out - -def split_huge_code_block(block: str) -> List[str]: - if "function" in block and "(define" in block: - return split_code_by_lines(block, 15) - lines = block.split("\n") - return split_code_by_lines(block, HUGE_CODE_LINES) - -def clean_text_noise(txt: str) -> str: - txt = re.sub(r'\b\w*_example(_\d+)?\b', '', txt) - lines = [l for l in txt.splitlines() if l.strip()] - uniq = [] - for l in lines: - if not uniq or l.strip() != uniq[-1].strip(): - uniq.append(l) - return "\n".join(uniq).strip() - -def is_duplicate_code(a: str, b: str) -> bool: - return SequenceMatcher(None, a.strip(), b.strip()).ratio() > 0.8 - -def chunk_by_tokens(text: str) -> List[str]: - segs = detect_code_blocks(clean_text_noise(text)) - chunks, buf, tok = [], [], 0 - - def flush(): - nonlocal buf, tok - if buf: - chunks.append(" ".join(buf).strip()) - buf = [] - tok = 0 - - for t, seg in segs: - if not seg.strip(): continue - if t == "code": - seg = seg.strip() - seg_toks = num_tokens(seg) - code_parts = [seg] if seg_toks <= MAX_TOKENS else split_huge_code_block(seg) - # dedupe similar adjacent code blocks (Lisp vs JS) - if buf and any(is_duplicate_code(seg, b) for b in buf if b.startswith("(") or b.startswith("function")): - continue - for part in code_parts: - part_toks = num_tokens(part) - if tok + part_toks > MAX_TOKENS: flush() - buf.append(part) - tok += part_toks - if tok >= MAX_TOKENS: flush() +def gather_code(node: ET.Element) -> str: + parts: List[str] = [] + + def visit(el: ET.Element) -> None: + tag = el.tag.upper() + if tag in DROP_TAGS or tag.startswith(SCHEME_PREFIXES): + return + if tag == "JAVASCRIPT_OUTPUT": + return + if el.text: + parts.append(el.text) + for child in el: + visit(child) + if child.tail: + parts.append(child.tail) + + visit(node) + return normalize_code(html.unescape("".join(parts))) + + +def is_text_container(node: ET.Element) -> bool: + return any(child.tag.upper() == "TEXT" for child in node) + + +def extract_segments_from_text(node: ET.Element) -> List[Dict[str, str]]: + segments: List[Dict[str, str]] = [] + buffer: List[str] = [] + + def append_text(value: Optional[str]) -> None: + if value: + buffer.append(html.unescape(value)) + + def flush_text() -> None: + if not buffer: + return + text = normalize_whitespace(" ".join(buffer)) + if text: + segments.append({"type": "text", "content": text}) + buffer.clear() + + def walk(el: ET.Element) -> None: + tag = el.tag.upper() + + if tag in DROP_TAGS or tag.startswith(SCHEME_PREFIXES): + return + + if tag in {"UL", "OL"}: + for li in el: + if li.tag.upper() == "LI": + append_text("\n- ") + walk(li) + if li.tail: + append_text(li.tail) + return + + if tag == "LI": + append_text("\n- ") + + if tag in CODE_BLOCK_TAGS: + flush_text() + code_block = gather_code(el) + if code_block: + segments.append({"type": "code", "content": code_block}) + return + + if tag == "JAVASCRIPT" and not is_text_container(el): + snippet = gather_code(el) + if not snippet: + return + if "\n" in snippet or re.search(r"[;{}=]", snippet): + flush_text() + segments.append({"type": "code", "content": snippet}) + else: + append_text(snippet) + return + + if el.text: + append_text(el.text) + + for child in el: + walk(child) + if child.tail: + append_text(child.tail) + + if node.text: + append_text(node.text) + + for child in node: + walk(child) + if child.tail: + append_text(child.tail) + + flush_text() + return segments + + +def extract_title(root: ET.Element) -> str: + name = root.find("NAME") + if name is None: + return "Untitled" + title = " ".join(name.itertext()) + return re.sub(r"\s+", " ", html.unescape(title)).strip() + + +def extract_segments( + root: ET.Element, + context: Dict[str, Optional[str]], + source_file: str, +) -> List[Dict[str, Optional[str]]]: + results: List[Dict[str, Optional[str]]] = [] + for order, text_node in enumerate(root.findall(".//TEXT"), start=1): + for segment in extract_segments_from_text(text_node): + entry = { + "chapter": context.get("chapter"), + "section": context.get("section"), + "subsection": context.get("subsection"), + "content_type": segment["type"], + "content": segment["content"], + "source_file": os.path.basename(source_file), + "order": order, + } + results.append(entry) + return results + + +def parse_xml_file( + file_path: str, + context: Optional[Dict[str, Optional[str]]] = None, + depth: int = 0, + visited: Optional[set] = None, +) -> List[Dict[str, Optional[str]]]: + if visited is None: + visited = set() + + real_path = os.path.abspath(file_path) + if real_path in visited or not os.path.exists(file_path): + return [] + visited.add(real_path) + + try: + tree = ET.parse(file_path) + except Exception as exc: + print(f"⚠️ XML parse error in {file_path}: {exc}") + return [] + + root = tree.getroot() + prune_tree(root) + + tag_type = root.tag.upper() + title = extract_title(root) + + ctx = dict(context or {"chapter": None, "section": None, "subsection": None}) + if tag_type == "CHAPTER": + ctx["chapter"] = title + ctx["section"] = None + ctx["subsection"] = None + elif tag_type == "SECTION": + ctx["section"] = title + ctx["subsection"] = None + elif tag_type == "SUBSECTION": + ctx["subsection"] = title + + segments = extract_segments(root, ctx, file_path) + + xml_text = html.unescape(ET.tostring(root, encoding="unicode")) + section_refs = re.findall(r"§ion([\d\.]+);", xml_text) + subsection_refs = re.findall(r"&subsection([\d\.]+);", xml_text) + + base_dir = os.path.dirname(file_path) + + for ref in section_refs: + section_id = ref.split(".")[-1] + section_folder = os.path.join(base_dir, f"section{section_id}") + section_file = os.path.join(section_folder, f"section{section_id}.xml") + segments.extend(parse_xml_file(section_file, ctx, depth + 1, visited)) + + for ref in subsection_refs: + subsection_id = ref.split(".")[-1] + subsection_file = os.path.join(base_dir, f"subsection{subsection_id}.xml") + segments.extend(parse_xml_file(subsection_file, ctx, depth + 1, visited)) + + return segments + + +# ---------- CHUNK HELPERS ---------- +VARIANT_REPLACEMENTS = [ + (re.compile(r"\bprocedures?\s+functions\b", re.IGNORECASE), "functions"), + (re.compile(r"\bprocedure\s+function\b", re.IGNORECASE), "function"), + (re.compile(r"\bexpression\s+statement\b", re.IGNORECASE), "statement"), + (re.compile(r"\bExpressions?\s+Statements?\b", re.IGNORECASE), "Statements"), + (re.compile(r"\bprocedure\s+applications?\b", re.IGNORECASE), "function application"), + (re.compile(r"\bProcedures?\s+Applications?\b"), "Function Applications"), + (re.compile(r"\bLisp\b", re.IGNORECASE), ""), + (re.compile(r"\bScheme\b", re.IGNORECASE), ""), +] + + +def normalize_sentence(sentence: str) -> str: + s = sentence.strip() + for pattern, replacement in VARIANT_REPLACEMENTS: + s = pattern.sub(replacement, s) + s = re.sub(r"\s{2,}", " ", s) + s = re.sub(r"\s+\.", ".", s) + return s.strip() + + +def expand_segment(segment: Dict[str, str]) -> Iterable[Dict[str, str]]: + seg_type = segment.get("content_type") + content = (segment.get("content") or "").strip() + if not content: + return [] + if seg_type == "code": + code = "\n".join(line.rstrip() for line in content.splitlines()) + if not code: + return [] + return [{"type": "code", "text": code}] + + text = normalize_whitespace(content) + sentences = safe_sentence_tokenize(text) + units = [] + seen = set() + for sentence in sentences: + s = normalize_sentence(sentence) + if not s: + continue + key = s.lower() + if key in seen: continue + seen.add(key) + units.append({"type": "text", "text": s}) + return units + + +def join_units(units: List[str], types: List[str]) -> str: + parts: List[str] = [] + for text, ttype in zip(units, types): + if ttype == "code": + parts.append(f"```javascript\n{text}\n```") + else: + parts.append(text) + out = "\n\n".join(parts) + out = re.sub(r"\n{3,}", "\n\n", out) + return out.strip() + + +def chunk_units( + units: List[Dict[str, str]], + meta: Dict[str, Optional[str]], +) -> List[Dict[str, object]]: + buffer: List[str] = [] + types: List[str] = [] + carryover: List[str] = [] + carry_pending = False + current_tokens = 0 + chunks: List[Dict[str, object]] = [] + + def seed_carry() -> None: + nonlocal carry_pending, current_tokens + if not (carry_pending and carryover): + return + for text in carryover: + buffer.append(text) + types.append("text") + current_tokens += num_tokens(text) + carry_pending = False + + def compute_carry() -> None: + carryover.clear() + if not OVERLAP_SENTENCES: + return + text_units = [text for text, ttype in zip(buffer, types) if ttype == "text"] + if not text_units: + return + tail = text_units[-OVERLAP_SENTENCES:] + carryover.extend(tail) + + def flush() -> None: + nonlocal buffer, types, current_tokens, carry_pending + if not buffer: + return + content = join_units(buffer, types) + token_count = num_tokens(content) + chunks.append( + { + "content": content, + "token_count": token_count, + "has_code": any(t == "code" for t in types), + **meta, + } + ) + compute_carry() + buffer = [] + types = [] + current_tokens = 0 + carry_pending = bool(carryover) + + for unit in units: + text = unit["text"] + ttype = unit["type"] + unit_tokens = num_tokens(text) + + if carry_pending and not buffer: + seed_carry() + + if current_tokens + unit_tokens > MAX_TOKENS and buffer: + flush() + if carry_pending and not buffer: + seed_carry() + + buffer.append(text) + types.append(ttype) + current_tokens += unit_tokens + + flush() + return chunks - for s in safe_sentence_tokenize(seg): - s = s.strip() - if not s: continue - stoks = num_tokens(s) - if stoks > MAX_TOKENS: - words = s.split() - for i in range(0,len(words),HARD_SENT_WORD_SPLIT): - sub = " ".join(words[i:i+HARD_SENT_WORD_SPLIT]) - if tok + num_tokens(sub) > MAX_TOKENS: flush() - buf.append(sub) - tok += num_tokens(sub) - if tok >= MAX_TOKENS: flush() - continue - if tok + stoks > MAX_TOKENS: flush() - buf.append(s) - tok += stoks - if buf: flush() - return [c for c in chunks if c.strip()] - -def make_chunk_key(e): return (e.get("title") or "", e.get("parent_title") or "", e.get("source_file") or "") # ---------- MAIN ---------- +def collect_segments() -> List[Dict[str, Optional[str]]]: + segments: List[Dict[str, Optional[str]]] = [] + chapter_dirs = sorted(glob(os.path.join(SICP_XML_DIR, "chapter*"))) + for chapter_dir in chapter_dirs: + if not os.path.isdir(chapter_dir): + continue + chapter_xml = os.path.join(chapter_dir, f"{os.path.basename(chapter_dir)}.xml") + if not os.path.exists(chapter_xml): + continue + segments.extend(parse_xml_file(chapter_xml)) + return segments + + def main(): - all_chunks = [] - files = sorted(glob(os.path.join(CHAPTER_JSON_DIR, CHAPTER_FILE_GLOB))) - print(f"📚 Processing {len(files)} chapter files...") - - for fpath in files: - with open(fpath, "r", encoding="utf-8") as f: - data = json.load(f) - - data = [d for d in data if isinstance(d, dict) and d.get("content")] - data.sort(key=lambda x: (x.get("parent_title") or "", x.get("title") or "", x.get("paragraph_index", 0))) - - for (title, parent, src), grp in groupby(data, key=make_chunk_key): - paras = [] - seen = set() - for p in grp: - txt = clean_text_noise(p["content"]) - if txt and txt not in seen: - seen.add(txt) - paras.append(txt) - merged = "\n".join(paras) - if not merged.strip(): continue - - subs = chunk_by_tokens(merged) - base = os.path.splitext(os.path.basename(fpath))[0] - safe_title = (title or "section").replace(" ", "_")[:60] - for i, ch in enumerate(subs, 1): - all_chunks.append({ - "chapter_file": os.path.basename(fpath), - "section": parent or None, - "subsection": title or None, - "chunk_id": f"{base}_{safe_title}_{i}", - "chunk_index": i, - "content": ch, - "token_count": num_tokens(ch), - "source_files": [src] - }) - - with open(OUTPUT_FILE, "w", encoding="utf-8") as out: - json.dump(all_chunks, out, indent=2, ensure_ascii=False) + segments = collect_segments() + if not segments: + print("⚠️ No segments parsed from XML.") + return + + segments.sort( + key=lambda s: ( + s.get("chapter") or "", + s.get("section") or "", + s.get("subsection") or "", + s.get("order") or 0, + ) + ) + + all_chunks: List[Dict[str, object]] = [] + + def group_key(item: Dict[str, Optional[str]]) -> tuple: + return ( + item.get("chapter") or "", + item.get("section") or "", + item.get("subsection") or "", + ) + + for key, group in groupby(segments, key=group_key): + chapter, section, subsection = key + group_list = list(group) + units: List[Dict[str, str]] = [] + for seg in group_list: + units.extend(expand_segment(seg)) + + if not units: + continue + + slug_source = subsection or section or chapter or "section" + slug = re.sub(r"[^\w\s-]", "", slug_source or "") + slug = re.sub(r"\s+", "_", slug.strip())[:60] or "section" + chapter_slug = re.sub(r"[^\w\s-]", "", chapter or "chapter") + chapter_slug = re.sub(r"\s+", "_", chapter_slug.strip())[:40] or "chapter" + + meta = { + "chapter": chapter or None, + "section": section or None, + "subsection": subsection or None, + } + + group_chunks = chunk_units(units, meta) + + for idx, chunk in enumerate(group_chunks, start=1): + chunk["chunk_index"] = idx + chunk["chunk_id"] = f"{chapter_slug}_{slug}_{idx}" + all_chunks.append(chunk) + + with open(OUTPUT_FILE, "w", encoding="utf-8") as handle: + json.dump(all_chunks, handle, indent=2, ensure_ascii=False) if all_chunks: - avg = sum(c["token_count"] for c in all_chunks)/len(all_chunks) + avg = sum(c["token_count"] for c in all_chunks) / len(all_chunks) print(f"✅ Created {len(all_chunks)} chunks → {OUTPUT_FILE}") print(f"📊 Avg tokens: {avg:.1f}, Max: {max(c['token_count'] for c in all_chunks)}") else: print("⚠️ No chunks produced.") + if __name__ == "__main__": main() diff --git a/parser/sicp_mesochunks_semantic_rag.json b/parser/sicp_mesochunks_semantic_rag.json index b833905a7..704d5ce47 100644 --- a/parser/sicp_mesochunks_semantic_rag.json +++ b/parser/sicp_mesochunks_semantic_rag.json @@ -1,1250 +1,4102 @@ [ { - "chapter_file": "chapter1_chunks.json", + "content": "We concentrated in chapter on computational processes and on the\nrole of\nfunctions\nin program design.\n\nWe saw how to use primitive data (numbers) and primitive\noperations (arithmetic operations), how to combine\nfunctions\nto form compound\nfunctions\nthrough composition, conditionals, and the use of parameters, and how to\nabstract\nprocesses\nby using\nfunction declarations.\n\nWe saw that a\nfunction\ncan be regarded as a pattern for the local evolution of a process, and we\nclassified, reasoned about, and performed simple algorithmic analyses of\nsome common patterns for processes as embodied in\nfunctions.\n\nWe also saw that higher-order\nfunctions\nenhance the power of our language by enabling us to manipulate, and thereby\nto reason in terms of, general methods of computation.\n\nThis is much of the\nessence of programming.\n\nIn this chapter we are going to look at more complex data.\n\nAll the\nfunctions\nin chapter operate on simple numerical data, and simple data are\nnot sufficient for many of the problems we wish to address using\ncomputation.\n\nPrograms are typically designed to model complex phenomena,\nand more often than not one must construct computational objects that have\nseveral parts in order to model real-world phenomena that have several\naspects.\n\nThus, whereas our focus in chapter was on building\nabstractions by combining\nfunctions\nto form compound\nfunctions,\nwe turn in this chapter to another key aspect of any programming language:\nthe means it provides for building abstractions by combining data objects\nto form compound data.\n\nWhy do we want compound data in a programming language?\n\nFor the same\nreasons that we want compound\nfunctions:\nto elevate the conceptual level at which we can design our programs, to\nincrease the modularity of our designs, and to enhance the expressive power\nof our language.", + "token_count": 291, + "has_code": false, + "chapter": "Building Abstractions with Data", "section": null, - "subsection": "Building Abstractions with Procedures Functions", - "chunk_id": "chapter1_chunks_Building_Abstractions_with_Procedures_Functions_1", + "subsection": null, "chunk_index": 1, - "content": "We are about to study the idea of a computational process . data . program . People create programs to direct processes. In effect, we conjure the spirits of the computer with our spells. A computational process is indeed much like a sorcerer s idea of a spirit. It cannot be seen or touched. It is not composed of matter at all. However, it is very real. It can perform intellectual work. It can answer questions. It can affect the world by disbursing money at a bank or by controlling a robot arm in a factory. The programs we use to conjure processes are like a sorcerer s spells. They are carefully composed from symbolic expressions in arcane and esoteric programming languages\nA computational process, in a correctly working computer, executes programs precisely and accurately. Thus, like the sorcerer s apprentice, novice programmers must learn to understand and to anticipate the consequences of their conjuring. Even small errors (usually called bugs or glitches ) (usually called bugs ) in programs can have complex and unanticipated consequences. Fortunately, learning to program is considerably less dangerous than learning sorcery, because the spirits we deal with are conveniently contained in a secure way. Real-world programming, however, requires care, expertise, and wisdom. A small bug in a computer-aided design program, for example, can lead to the catastrophic collapse of an airplane or a dam or the self-destruction of an industrial robot.", - "token_count": 287, - "source_files": [ - "chapter1.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Building_Abstractions_with_Data_1" }, { - "chapter_file": "chapter1_chunks.json", + "content": "For the same\nreasons that we want compound\nfunctions:\nto elevate the conceptual level at which we can design our programs, to\nincrease the modularity of our designs, and to enhance the expressive power\nof our language.\n\nJust as the ability to\ndeclare functions\nenables us to deal with processes at a higher conceptual level than that of\nthe primitive operations of the language, the ability to construct compound\ndata objects enables us to deal with data at a higher conceptual level than\nthat of the primitive data objects of the language.\n\nConsider the task of designing a system to perform\nadd_rat\nthat takes two rational numbers and produces their sum.\n\nIn terms of\nsimple data, a rational number can be thought of as two integers: a\nnumerator and a denominator.\n\nThus, we could design a program in which\neach rational number would be represented by two integers (a numerator\nand a denominator) and where\nadd_rat\nwould be implemented by two\nfunctions\n(one producing the numerator of the sum and one producing\nthe denominator).\n\nBut this would be awkward, because we would then\nneed to explicitly keep track of which numerators corresponded to\nwhich denominators.\n\nIn a system intended to perform many operations\non many rational numbers, such bookkeeping details would clutter the\nprograms substantially, to say nothing of what they would do to our\nminds.\n\nIt would be much better if we could glue together\na numerator and denominator to form a pair a compound data\nobject that our programs could manipulate in a way that would\nbe consistent with regarding a rational number as a single conceptual\nunit.\n\nThe use of compound data also enables us to increase the modularity of\nour programs.", + "token_count": 285, + "has_code": false, + "chapter": "Building Abstractions with Data", "section": null, - "subsection": "Building Abstractions with Procedures Functions", - "chunk_id": "chapter1_chunks_Building_Abstractions_with_Procedures_Functions_2", + "subsection": null, "chunk_index": 2, - "content": "Master software engineers have the ability to organize programs so that they can be reasonably sure that the resulting processes will perform the tasks intended. They can visualize the behavior of their systems in advance. They know how to structure programs so that unanticipated problems do not lead to catastrophic consequences, and when problems do arise, they can debug their programs. Well-designed computational systems, like well-designed automobiles or nuclear reactors, are designed in a modular manner, so that the parts can be constructed, replaced, and debugged separately. We need an appropriate language for describing processes, and we will use for this purpose the programming language Lisp. Just as our everyday thoughts are usually expressed in our natural language (such as English, Swedish, or German), and descriptions of quantitative phenomena are expressed with mathematical notations, our procedural thoughts will be expressed in Lisp. recursion equations , as a model for computation. The language was conceived by Recursive Functions of Symbolic Expressions and Their Computation by Machine (\nWe need an appropriate language for describing processes, and we will use for this purpose the programming language JavaScript. Just as our everyday thoughts are usually expressed in our natural language (such as English, Swedish, or Chinese), and descriptions of quantitative phenomena are expressed with mathematical notations, our procedural thoughts will be expressed in JavaScript. Mocha , which was later renamed to LiveScript , and finally to JavaScript. JavaScript is a trademark of Oracle Corporation. Despite its inception as a mathematical formalism, Lisp is a practical programming language.", - "token_count": 298, - "source_files": [ - "chapter1.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Building_Abstractions_with_Data_2" }, { - "chapter_file": "chapter1_chunks.json", + "content": "The use of compound data also enables us to increase the modularity of\nour programs.\n\nIf we can manipulate rational numbers directly as\nobjects in their own right, then we can separate the part of our\nprogram that deals with rational numbers per se from the details of\nhow rational numbers may be represented as pairs of integers.\n\nThe\ngeneral technique of isolating the parts of a program that deal with\nhow data objects are represented from the parts of a program that deal\nwith how data objects are used is a powerful design methodology called\ndata abstraction.\n\nWe will see how data abstraction makes\nprograms much easier to design, maintain, and modify.\n\nThe use of compound data leads to a real increase in the expressive power\nof our programming language.\n\nConsider the idea of forming a\nlinear combination $ax+by$.\n\nWe\nmight like to write a\nfunction\nthat would accept $a$ ,\n$b$ , $x$ , and\n$y$ as arguments and return the value of\n$ax+by$.\n\nThis presents no difficulty if the\narguments are to be numbers, because we can readily\ndeclare the function\n\n```javascript\nlinear_combination_example\n\nfunction linear_combination(a, b, x, y) {\n return a * x + b * y;\n}\n```\n\n```javascript\nlinear_combination_example\n\n\tlinear_combination(1, 2, 3, 4);\n```\n\nBut suppose we are not concerned only with numbers.\n\nSuppose we would like to\ndescribe a process that forms\nlinear combinations whenever addition and multiplication are\ndefined for rational numbers, complex numbers, polynomials, or\nwhatever.\n\nWe could express this as a\nfunction\nof the form\n\n```javascript\nfunction linear_combination(a, b, x, y) {\n return add(mul(a, x), mul(b, y));\n}\n```\n\nwhere functions linear_combination should need to know about functions function linear_combination, it is irrelevant what function such as linear_combination to pass its arguments along to\n\nWe begin this chapter by implementing the rational-number arithmetic system\nmentioned above.", + "token_count": 305, + "has_code": true, + "chapter": "Building Abstractions with Data", "section": null, - "subsection": "Building Abstractions with Procedures Functions", - "chunk_id": "chapter1_chunks_Building_Abstractions_with_Procedures_Functions_3", + "subsection": null, "chunk_index": 3, - "content": "A Lisp interpreter is a machine that carries out processes described in the Lisp language. The first Lisp interpreter was implemented by\nDespite its inception as a language for scripting the web, JavaScript interpreter is a machine that carries out processes described in the JavaScript language. Lisp was not the product of a concerted design effort. Instead, it evolved informally in an experimental manner in response to users needs and to pragmatic implementation considerations. Lisp s informal evolution has continued through the years, and the community of Lisp users has traditionally resisted attempts to promulgate any official definition of the language. This evolution, together with the flexibility and elegance of the initial conception, has enabled Lisp, which is the second oldest language in widespread use today (only\nJavaScript bears only superficial resemblance to the language Java, after which it was (eventually) named; both Java and JavaScript use the block structure of the language C. In contrast with Java and C, which usually employ compilation to lower-level languages, JavaScript programs were initially interpreted by web browsers. s Internet Explorer, whose JavaScript version is called JScript . The popularity of JavaScript for controlling web browsers gave rise to a standardization effort, culminating in ECMAScript . The and completed in June 1997 (\nBecause of its experimental character and its emphasis on symbol manipulation,\nThe practice of embedding JavaScript programs in web pages encouraged the developers of web browsers to implement JavaScript interpreters.", - "token_count": 279, - "source_files": [ - "chapter1.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Building_Abstractions_with_Data_3" }, { - "chapter_file": "chapter1_chunks.json", + "content": "We begin this chapter by implementing the rational-number arithmetic system\nmentioned above.\n\nThis will form the background for our discussion of\ncompound data and data abstraction.\n\nAs with compound\nfunctions,\nthe main issue to be addressed is that of abstraction as a technique for\ncoping with complexity, and we will see how data abstraction enables us to\nerect suitable\nabstraction barriers\nbetween different parts of a program.\n\nWe will see that the key to forming compound data is that a programming\nlanguage should provide some kind of glue so that data\nobjects can be combined to form more complex data objects.\n\nThere are\nmany possible kinds of glue.\n\nIndeed, we will discover how to form compound\ndata using no special data operations at all, only\nfunctions.\n\nThis will further blur the distinction between\nfunction\nand data, which was already becoming tenuous toward the end\nof chapter.\n\nWe will also explore some conventional techniques for\nrepresenting sequences and trees.\n\nOne key idea in dealing with compound\ndata is the notion of\nclosure that the\nglue we use for combining data objects should allow us to combine not only\nprimitive data objects, but compound data objects as well.\n\nAnother key idea\nis that compound data objects can serve as\nconventional interfaces for combining program modules in\nmix-and-match ways.\n\nWe illustrate some of these ideas by presenting a\nsimple graphics language that exploits closure.\n\nWe will then augment the representational power of our language by\nintroducing\nsymbolic expressions data whose elementary parts\ncan be arbitrary symbols rather than only numbers.\n\nWe explore various\nalternatives for representing sets of objects.", + "token_count": 266, + "has_code": false, + "chapter": "Building Abstractions with Data", "section": null, - "subsection": "Building Abstractions with Procedures Functions", - "chunk_id": "chapter1_chunks_Building_Abstractions_with_Procedures_Functions_4", + "subsection": null, "chunk_index": 4, - "content": "As these programs became more complex, the interpreters became more efficient in executing them, eventually using sophisticated implementation techniques such as Just-In-Time (JIT) compilation. The majority of JavaScript programs as of this writing (2021) are embedded in web pages and interpreted by browsers, but JavaScript is increasingly used as a general-purpose programming language, using systems such as Node.js. If Lisp is not a mainstream language, why are we using it as the framework for our discussion of programming? Because the language possesses procedures , can themselves be represented and manipulated as Lisp data. The importance of this is that there are powerful program-design techniques that rely on the ability to blur the traditional distinction between passive data and active processes. As we shall discover, Lisp s flexibility in handling procedures as data makes it one of the most convenient languages in existence for exploring these techniques. The ability to represent procedures as data also makes Lisp an excellent language for writing programs that must manipulate other programs as data, such as the interpreters and compilers that support computer languages. Above and beyond these considerations, programming in Lisp is great fun.", - "token_count": 219, - "source_files": [ - "chapter1.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Building_Abstractions_with_Data_4" + }, + { + "content": "We explore various\nalternatives for representing sets of objects.\n\nWe will find that,\njust as a given numerical function can be computed by many different\ncomputational processes, there are many ways in which a given data\nstructure can be represented in terms of simpler objects, and the\nchoice of representation can have significant impact on the time and\nspace requirements of processes that manipulate the data.\n\nWe will\ninvestigate these ideas in the context of symbolic differentiation,\nthe representation of sets, and the encoding of information.\n\nNext we will take up the problem of working with data that may be\nrepresented differently by different parts of a program.\n\nThis leads\nto the need to implement\ngeneric operations , which must handle many different types of data.\n\nMaintaining modularity in the presence of generic operations requires more\npowerful abstraction barriers than can be erected with simple data\nabstraction alone.\n\nIn particular, we introduce data-directed\nprogramming as a technique that allows individual data representations\nto be designed in isolation and then combined\nadditively (i.e., without modification).\n\nTo illustrate the power\nof this approach to system design, we close the chapter by applying what we\nhave learned to the implementation of a package for performing symbolic\narithmetic on polynomials, in which the coefficients of the polynomials can\nbe integers, rational numbers, complex numbers, and even other polynomials.", + "token_count": 224, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": null, + "subsection": null, + "chunk_index": 5, + "chunk_id": "Building_Abstractions_with_Data_Building_Abstractions_with_Data_5" }, { - "chapter_file": "chapter1_chunks.json", - "section": "Building Abstractions with Procedures Functions", - "subsection": "The Elements of Programming", - "chunk_id": "chapter1_chunks_The_Elements_of_Programming_1", + "content": "As we have seen, pairs provide a primitive glue that we can\nuse to construct compound data objects.\n\nFigure\nshows a standard way to visualize a\nin this case, the pair formed by\npair(1, 2).\n\n```javascript\nIn this representation, which is called\n\tbox-and-pointer\n notation, each compound object is shown as a\n\tpointer to a box. The box for a pair\n\thas two parts, the left part containing the head of the pair and the\n\tright part containing the tail.\n```\n\nWe have already seen that\npair\ncan be used to combine not only numbers but pairs as well.\n\n(You made use\nof this fact, or should have, in doing\nexercises\nand.) As a consequence, pairs provide\na universal building block from which we can construct all sorts of data\nstructures.\n\nFigure\nshows two ways to use pairs to combine the numbers 1, 2, 3, and 4.\n\nTwo ways to combine 1, 2, 3, and 4 using pairs.\n\nThe ability to create pairs whose elements are pairs is the essence of\nlist structure s importance as a representational tool.\n\nWe refer to\nthis ability as the\nclosure property of\npair.\n\nIn general, an operation for combining data objects satisfies the closure\nproperty if the results of combining things with that operation can\nthemselves be combined using the same operation. hierarchical structures structures made up of parts, which\nthemselves are made up of parts, and so on.\n\nFrom the outset of chapter , we ve made essential use of\nclosure in dealing with\nfunctions,\nbecause all but the very simplest programs rely on the fact that the\nelements of a combination can themselves be combinations.\n\nIn this section,\nwe take up the consequences of closure for compound data.", + "token_count": 286, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": null, "chunk_index": 1, - "content": "A powerful 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. Thus, 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: primitive expressions , cerned with, means of combination , by means of abstraction ,\nIn programming, we deal with two kinds of elements: and stuff that we want to manipulate, and procedures functions are descriptions of the rules for manipulating the data. Thus, any powerful programming language should be able to describe primitive data and primitive procedures functions and should have methods for combining and abstracting procedures functions and data. In this chapter we will deal only with simple procedures functions . procedures functions to manipulate compound data as well.", - "token_count": 177, - "source_files": [ - "section1.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Hierarchical_Data_and_the_Closure_Property_1" }, { - "chapter_file": "chapter1_chunks.json", - "section": "The Elements of Programming", - "subsection": "Expressions", - "chunk_id": "chapter1_chunks_Expressions_1", + "content": "In this section,\nwe take up the consequences of closure for compound data.\n\nWe describe some\nconventional techniques for using pairs to represent sequences and trees,\nand we exhibit a graphics language that illustrates closure in a vivid\nway.", + "token_count": 39, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Data_Hierarchical_Data_and_the_Closure_Property_2" + }, + { + "content": "This section presents a simple language for drawing pictures that\nillustrates the power of data abstraction and closure, and also exploits\nhigher-order\nfunctions\nin an essential way.\n\nThe language is designed to make it easy to\nexperiment with patterns such as the ones in\nfigure , which are composed of\nrepeated elements that are shifted and scaled.\nfunctions\nrather than as list structure.\n\nJust as\npair,\nwhich satisfies the\nDesigns generated with the picture language.\n\nWhen we began our study of programming in\nsection , we emphasized the\nimportance of describing a language by focusing on the language s\nprimitives, its means of combination, and its means of abstraction.\n\nWe ll follow that framework here.\n\nPart of the elegance of this picture language is that there is only one\nkind of element, called a\npainter.\n\nA painter draws an image that is shifted and scaled to\nfit within a designated\ns a primitive painter\nwe ll call.\n\nImages produced by the\nThe actual shape of the drawing depends on the frame all four\nimages in figure are produced by the same\ns founder, William Barton Rogers, as shown in\nfigure.\nare drawn with respect to the same four frames\nas the.\n\nImages of William Barton Rogers, founder and first\npresident of MIT, painted with respect to the same four frames as in\nfigure (original image courtesy MIT Museum).\n\nTo combine images, we use various\ns image\nin the left half of the frame and the second painter s image in the\nright half of the frame.\n\nSimilarly,\ns image below the\nsecond painter s image.\n\nSome operations transform a single painter\nto produce a new painter.", + "token_count": 276, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Example: A Picture Language", "chunk_index": 1, - "content": "One easy way to get started at programming is to examine some typical interactions with an interpreter for the Scheme dialect of Lisp. JavaScript language. Imagine that you are sitting at a computer terminal. You type an expression , a statement , and the interpreter responds by displaying the result of its evaluating that expression. One kind of statement you might type is an expression statement, which consists of an expression followed by a semicolon. (More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present Lisp with a number JavaScript with the program 486 486; the interpreter will respond by printing 486\nExpressions representing numbers may be combined with an + or * ) to form a compound expression that represents the application of the procedure to those numbers. For example, (+ 137 349) 486 (- 1000 334) 666 (* 5 99) 495 (/ 10 5) 2 (+ 2.7 10) 12.7", - "token_count": 206, - "source_files": [ - "subsection1.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_1" }, { - "chapter_file": "chapter1_chunks.json", - "section": "The Elements of Programming", - "subsection": "Expressions", - "chunk_id": "chapter1_chunks_Expressions_2", + "content": "Some operations transform a single painter\nto produce a new painter.\n\nFor example,\nflip_vert\ntakes a painter and produces a painter that draws its image upside-down, and\nflip_horiz\nproduces a painter that draws the original painter s image\nleft-to-right reversed.\n\nFigure shows the drawing of a painter called\n\n```javascript\nwave2\n wave2_example\n\nconst wave2 = beside(wave, flip_vert(wave));\nconst wave4 = below(wave2, wave2);\n\nconst heart2 = beside(heart, flip_vert(heart));\n// const heart4 = stack(heart2, heart2);\n```\n\n```javascript\nwave2_example\n\nwave2;\n\nshow(heart2);\n// show(heart4);\n```\n\nIn building up a complex image in this manner we are exploiting the fact\nthat painters are\ns means of combination.\n\nThe\npair,\nthe closure of our data under the means of combination is crucial to the\nability to create complex structures while using only a few operations.\n\nOnce we can combine painters, we would like to be able to abstract typical\npatterns of combining painters.\n\nWe will implement the painter operations as\nJavaScript functions.\n\nThis means that we don t need a special abstraction mechanism in the\npicture language: Since the means of combination are ordinary\nJavaScript functions,\nwe automatically have the capability to do anything with painter operations\nthat we can do with\nfunctions.\n\nFor example, we can abstract the pattern in\n\n```javascript\nflipped_pairs\n wave4_2\n\nfunction flipped_pairs(painter) {\n const painter2 = beside(painter, flip_vert(painter));\n return below(painter2, painter2);\n}\n\nfunction flipped_pairs(painter) {\n const painter2 = beside(painter, flip_vert(painter));\n return stack(painter2, painter2);\n}\n```\n\nand declare\n\n```javascript\nwave4_2\n flipped_pairs\n\nconst wave4 = flipped_pairs(wave);\n\nconst heart4 = flipped_pairs(heart);\nshow(heart4);\n```\n\n```javascript\nRecursive plans for\n\t right_split and\n\t corner_split.\n```\n\nWe can also define recursive operations.\n\nHere s one that makes\npainters split and branch towards the right as shown in\nfigures\nand\n:", + "token_count": 279, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Example: A Picture Language", "chunk_index": 2, - "content": "Expressions representing numbers may be combined with operators (such + * ) to form a 137 + 349; 486 1000 - 334; 666 5 * 99; 495 10 / 4; 2.5 2.7 + 10; 12.7\nExpressions such as these, formed by combinations . The leftmost element in the list is called the operator , and the other elements are called operands . The arguments that are the values of the operands. Expressions such as these, which contain other expressions as components, are called combinations . operator symbol in the middle, and operand expressions to the left and right of it, are called operator combinations . The convention of placing the operator to the left of the operands is known as prefix notation , and it may be somewhat confusing at first because it departs significantly from the customary mathematical convention. Prefix notation has several advantages, however. One of them is that it can accommodate (+ 21 35 12 7) 75 (* 25 4 12) 1200 No ambiguity can arise, because the operator is always the leftmost element and the entire combination is delimited by the parentheses.", - "token_count": 242, - "source_files": [ - "subsection1.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_2" }, { - "chapter_file": "chapter1_chunks.json", - "section": "The Elements of Programming", - "subsection": "Expressions", - "chunk_id": "chapter1_chunks_Expressions_3", + "content": "Here s one that makes\npainters split and branch towards the right as shown in\nfigures\nand\n:\n\n```javascript\nright_split\n right_split_example_1\n\nfunction right_split(painter, n) {\n if (n === 0) {\n return painter;\n } else {\n const smaller = right_split(painter, n - 1);\n return beside(painter, below(smaller, smaller));\n }\n}\n\nfunction right_split(painter, n) {\n if (n === 0) {\n return painter;\n } else {\n const smaller = right_split(painter, n - 1);\n return beside(painter, stack(smaller, smaller));\n }\n}\n```\n\nWe can produce balanced patterns by branching upwards as well as towards the right (see exercise and figures and ):\n\n```javascript\ncorner_split\n right_split\n up_split\n corner_split_example_1\n\nfunction corner_split(painter, n) {\n if (n === 0) {\n return painter;\n } else {\n const up = up_split(painter, n - 1);\n const right = right_split(painter, n - 1);\n const top_left = beside(up, up);\n const bottom_right = below(right, right);\n const corner = corner_split(painter, n - 1);\n return beside(below(painter, top_left),\n below(bottom_right, corner));\n }\n}\n\nfunction corner_split(painter, n) {\n if (n === 0) {\n return painter;\n } else {\n const up = up_split(painter, n - 1);\n const right = right_split(painter, n - 1);\n const top_left = beside(up, up);\n const bottom_right = stack(right, right);\n const corner = corner_split(painter, n - 1);\n return stack(beside(top_left, corner),\n beside(painter, bottom_right));\n }\n}\n```\n\n```javascript\ncorner_split_example_1\n corner_split_example_1\n\nshow(corner_split(heart, 4));\n```\n\n```javascript\nright_split_example_1\n right_split\n\n right_split(wave, 4); // (a)\n right_split(rogers, 4); // (b)\n corner_split(wave, 4); // (c)\n corner_split(rogers, 4); // (d)\n\n show(right_split(heart, 4));\n```\n\n```javascript\nThe recursive operation\n right_split applied to the\n\t painters wave and\n rogers.\n Combining four\n corner_split figures\n\t produces symmetric\n\t square_limit\n as shown in figure.\n```\n\nBy placing four copies of a corner_split appropriately, we obtain a pattern called square_limit, whose application to :", + "token_count": 276, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Example: A Picture Language", "chunk_index": 3, - "content": "A second advantage of prefix notation is that it extends in a straightforward way to allow combinations to be nested , that is, to have combinations whose elements are themselves combinations: (+ (* 3 5) (- 10 6)) 19\nThe convention of placing the operator between the operands is known as infix notation . It follows the mathematical notation that you are most likely familiar with from school and everyday life. As in mathematics, operator combinations can be nested , that is, they can have operands that (3 * 5) + (10 - 6); 19 As usual, 3 * 5 + 10 / 2; stands for (3 * 5) + (10 / 2); We say that * and / have higher precedence than + and - . Sequences of additions and subtractions are read from left to right, as are sequences of multiplications and divisions. Thus, -6 1 - 5 / 2 * 4 + 3; stands for (1 - ((5 / 2) * 4)) + 3; We say that the operators + , - , * and / are left-associative . There is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the Lisp interpreter can evaluate.", - "token_count": 268, - "source_files": [ - "subsection1.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_3" }, { - "chapter_file": "chapter1_chunks.json", - "section": "The Elements of Programming", - "subsection": "Expressions", - "chunk_id": "chapter1_chunks_Expressions_4", + "content": "By placing four copies of a corner_split appropriately, we obtain a pattern called square_limit, whose application to :\n\n```javascript\nsquare_limit\n corner_split\n square_limit_example\n\nfunction square_limit(painter, n) {\n const quarter = corner_split(painter, n);\n const half = beside(flip_horiz(quarter), quarter);\n return below(flip_vert(half), half);\n}\n\nfunction square_limit(painter, n) {\n const quarter = corner_split(painter, n);\n const upper_half = beside(flip_horiz(quarter), quarter);\n const lower_half = beside(turn_upside_down(quarter),\n flip_vert(quarter));\n return stack(upper_half, lower_half);\n}\n```\n\n```javascript\nsquare_limit_example\n\nsquare_limit(rogers, 4);\n\nshow(square_limit(heart, 4));\n```\n\nIn addition to abstracting patterns of combining painters, we can work at a\nhigher level, abstracting patterns of combining painter operations.\n\nThat\nis, we can view the painter operations as elements to manipulate and can\nwrite means of combination for these\nelementsfunctions\nthat take painter operations as arguments and create new painter operations.\n\nFor example,\nflipped_pairs\nand\nsquare_limit\neach arrange four copies of a painter s image in a square pattern;\nthey differ only in how they orient the copies.\n\nOne way to abstract this\npattern of painter combination is with the following\nfunction,\nwhich takes four one-argument painter operations and produces a painter\noperation that transforms a given painter with those four operations and\narranges the results in a square.\n\nThe functions tl,\n\n```javascript\nsquare_of_four\n square_of_four_example\n\nfunction square_of_four(tl, tr, bl, br) {\n return painter => {\n const top = beside(tl(painter), tr(painter));\n const bottom = beside(bl(painter), br(painter));\n return below(bottom, top);\n };\n}\n\nfunction square_of_four(tl, tr, bl, br) {\n return painter => stack(beside(tl(painter), tr(painter)),\n beside(bl(painter), br(painter)));\n}\n```\n\n```javascript\nsquare_of_four_example\n identity\n\nsquare_of_four(flip_vert, identity,\n quarter_turn_right, quarter_turn_left)(rogers);\n\nshow(square_of_four(turn_upside_down, identity,\n quarter_turn_right, quarter_turn_left)\n (heart)\n );\n```\n\nThen\n\n```javascript\nflipped_pairs\n can be defined in terms of\n```\n\nsquare_of_four as follows:\n\n```javascript\nflipped_pairs_2\n square_of_four\n identity\n flipped_pairs_example\n\nfunction flipped_pairs(painter) {\n const combine4 = square_of_four(identity, flip_vert,\n identity, flip_vert);\n return combine4(painter);\n}\n\nfunction flipped_pairs(painter) {\n const combine4 = square_of_four(turn_upside_down, flip_vert,\n flip_horiz, identity);\n return combine4(painter);\n}\n```\n\n```javascript\nflipped_pairs_example\n\nflipped_pairs(rogers);\n\nshow(flipped_pairs(heart));\n```\n\nand square_limit can be expressed as", + "token_count": 308, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Example: A Picture Language", "chunk_index": 4, - "content": "It is we humans who get confused by still relatively simple expressions such as (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) following a formatting convention known as pretty-printing , in which each long combination is written so that the operands are aligned vertically. The resulting\nThere is no limit (in principle) to the depth of such nesting and to the overall complexity of the expressions that the JavaScript interpreter can evaluate. It is we humans who might get confused by still relatively simple expressions such as 57 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; which the interpreter would readily evaluate to be 57. We can help ourselves by writing such an expression in the form 3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; to visually separate the major components of the expression. Even with complex expressions, the interpreter always operates in the same basic cycle: It reads an expression from the terminal, a statement typed by the user, evaluates the expression, statement, and prints the result.", + "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_4" + }, + { + "content": "and square_limit can be expressed as\n\n```javascript\nsquare_limit_2\n square_of_four\n identity\n corner_split\n square_limit_example_2\n\nfunction square_limit(painter, n) {\n const combine4 = square_of_four(flip_horiz, identity,\n rotate180, flip_vert);\n return combine4(corner_split(painter, n));\n}\n\nfunction square_limit(painter, n) {\n const combine4 = square_of_four(flip_horiz, identity,\n turn_upside_down, flip_vert);\n return combine4(corner_split(painter, n));\n}\n```\n\n```javascript\nsquare_limit_example_2\n\nsquare_limit(rogers, 4)(full_frame);\n\nshow(square_limit(heart, 4));\n```\n\nBefore we can show how to implement painters and their means of\ncombination, we must first consider\nan origin vector\nand two edge vectors.\n\nThe origin vector specifies the offset of the\nframe s origin from some absolute origin in the plane, and the edge\nvectors specify the offsets of the frame s corners from its origin.\n\nIf the edges are perpendicular, the frame will be rectangular.\n\nOtherwise the frame will be a more general parallelogram.\n\nFigure shows a frame and its associated\nvectors.\n\nIn accordance with data abstraction, we need not be specific yet\nabout how frames are represented, other than to say that there is a\nconstructor\nmake_frame,\nwhich takes three vectors and produces a frame, and three corresponding\nselectors\norigin_frame,\nedge1_frame,\nand\nedge2_frame\n(see exercise ).\n\nA frame is described by three vectors an origin and two edges.\n\nWe will use coordinates in the\n$0\\leq x, y\\leq 1$ ) to specify images.\n\nWith\neach frame, we associate a\nframe coordinate map , which will be used to shift and scale images\nto fit the frame.\n\nThe map transforms the unit square into the frame by\nmapping the vector $\\mathbf{v}=(x, y)$ to the\nvector sum\n\\[\n\\text{Origin(Frame)} + x\\cdot \\text{ Edge}_1\\text{ (Frame)}\n+ y\\cdot \\text{ Edge}_2\\text{ (Frame)}\n\\]\nFor example, $(0, 0)$ is mapped to the origin of\nthe frame, $(1, 1)$ to the vertex diagonally\nopposite the origin, and $(0.5, 0.5)$ to the\ncenter of the frame.\n\nWe can create a frame s coordinate map with\nthe following\nfunction :", + "token_count": 303, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Example: A Picture Language", + "chunk_index": 5, + "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_5" + }, + { + "content": "We can create a frame s coordinate map with\nthe following\nfunction :\n\n```javascript\nframe_coord_map\n frame_coord_map_example\n [ 14, 18 ]\n frame_functions\n vector_functions\n\nfunction frame_coord_map(frame) {\n return v => add_vect(origin_frame(frame),\n add_vect(scale_vect(xcor_vect(v),\n edge1_frame(frame)),\n scale_vect(ycor_vect(v),\n edge2_frame(frame))));\n}\n```\n\n```javascript\nframe_coord_map_example\n\nconst my_origin = make_vect(1, 2);\nconst my_edge_1 = make_vect(3, 4);\nconst my_edge_2 = make_vect(5, 6);\nconst my_frame = make_frame(my_origin, my_edge_1, my_edge_2);\nconst my_coord_map = frame_coord_map(my_frame);\nconst my_vector = make_vect(1, 2);\nconst my_mapped_vector = my_coord_map(my_vector);\nmy_mapped_vector;\n```\n\nObserve that applying\nframe_coord_map\nto a frame returns a\nfunction\nthat, given a vector, returns a vector.\n\nIf the argument vector is in the\nunit square, the result vector will be in the frame.\n\nFor example,\n\n```javascript\nvector_example\n frame_functions\n vector_functions\n frame_coord_map\n [ 1, 2 ]\n\nframe_coord_map(a_frame)(make_vect(0, 0));\n\nconst a_frame = make_frame(make_vect(1, 2),\n make_vect(3, 4),\n make_vect(5, 5));\nframe_coord_map(a_frame)(make_vect(0, 0));\n```\n\nreturns the same vector as\n\n```javascript\nvector_example_2\n frame_functions\n [ 1, 2 ]\n\norigin_frame(a_frame);\n\nconst a_frame = make_frame(make_vect(1, 2),\n make_vect(3, 4),\n make_vect(5, 5));\norigin_frame(a_frame);\n```\n\nA painter is represented as a\nfunction\nthat, given a frame as argument, draws a particular image shifted and\nscaled to fit the frame.\n\nThat is to say, if\ns image in\n\nThe details of how primitive painters are implemented depend on the\nparticular characteristics of the graphics system and the type of image to\nbe drawn.\n\nFor instance, suppose we have a\nfunction\ndraw_line\nthat draws a line on the screen between two specified points.\n\nThen we can\ncreate painters for line drawings, such as the\nwave\npainter in figure , from lists of line\nsegments as follows:\n\n```javascript\ndraw_line\n\n// \"drawing a line\" here simulated\n// by printing the coordinates of\n// the start and end of the line\nfunction draw_line(v_start, v_end) {\n display(\"line starting at\");\n display(v_start);\n display(\"line ending at\");\n display(v_end);\n}\n```", + "token_count": 291, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Example: A Picture Language", + "chunk_index": 6, + "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_6" + }, + { + "content": "Then we can\ncreate painters for line drawings, such as the\nwave\npainter in figure , from lists of line\nsegments as follows:\n\n```javascript\nsegments_to_painter\n frame_coord_map\n segment_functions\n draw_line\n segments_to_painter_example\n\nfunction segments_to_painter(segment_list) {\n return frame =>\n for_each(segment =>\n draw_line(\n frame_coord_map(frame)\n (start_segment(segment)),\n frame_coord_map(frame)\n (end_segment(segment))),\n segment_list);\n}\n```\n\n```javascript\nsegments_to_painter_example\n\nconst my_origin = make_vect(1, 2);\nconst my_edge_1 = make_vect(3, 4);\nconst my_edge_2 = make_vect(5, 6);\nconst my_frame = make_frame(my_origin, my_edge_1, my_edge_2);\n\nconst my_start_1 = make_vect(0, 1);\nconst my_end_1 = make_vect(1, 1);\nconst my_segment_1 = make_segment(my_start_1, my_end_1);\n\nconst my_start_2 = make_vect(0, 2);\nconst my_end_2 = make_vect(2, 2);\nconst my_segment_2 = make_segment(my_start_2, my_end_2);\n\nconst my_painter = segments_to_painter(\n list(my_segment_1, my_segment_2));\n\nmy_painter(my_frame);\n```\n\nThe segments are given using coordinates with respect to the unit square.\n\nFor each segment in the list, the painter transforms the segment endpoints\nwith the frame coordinate map and draws a line between the transformed\npoints.\n\nRepresenting painters as\nfunctions\nerects a powerful abstraction barrier in the picture language.\n\nWe can\ncreate and intermix all sorts of primitive painters, based on a variety of\ngraphics capabilities.\n\nThe details of their implementation do not matter.\n\nAny\nfunction\ncan serve as a painter, provided that it takes a frame as argument and\ndraws something scaled to fit the frame.\n\nAn operation on painters (such as flip_vert or flip_vert doesn t have to know how a painter works in order to flip it it just\n\nhas to know how to turn a frame upside down: The flipped painter just uses the original painter, but in the inverted frame.\n\nPainter operations are based on the\nfunction\ntransform_painter,\nwhich takes as arguments a painter and information on how to transform a\nframe and produces a new painter.\n\nThe transformed painter, when called on\na frame, transforms the frame and calls the original painter on the\ntransformed frame.", + "token_count": 299, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Example: A Picture Language", + "chunk_index": 7, + "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_7" + }, + { + "content": "The transformed painter, when called on\na frame, transforms the frame and calls the original painter on the\ntransformed frame.\n\nThe arguments to\ntransform_painter\nare points (represented as vectors) that specify the corners of the new\nframe: When mapped into the frame, the first point specifies the new\nframe s origin and the other two specify the ends of its edge vectors.\n\nThus, arguments within the unit square specify a frame contained within the\noriginal frame.\n\n```javascript\ntransform_painter\n frame_functions\n vector_functions\n frame_coord_map\n flip_vert_example\n\nfunction transform_painter(painter, origin, corner1, corner2) {\n return frame => {\n const m = frame_coord_map(frame);\n const new_origin = m(origin);\n return painter(make_frame(\n new_origin,\n sub_vect(m(corner1), new_origin),\n sub_vect(m(corner2), new_origin)));\n };\n}\n```\n\nHere s how to flip painter images vertically:\n\n```javascript\nflip_vert\n transform_painter\n flip_vert_example\n\nfunction flip_vert(painter) {\n return transform_painter(painter,\n make_vect(0, 1), // new origin\n make_vect(1, 1), // new end of edge1\n make_vect(0, 0)); // new end of edge2\n}\n```\n\n```javascript\nflip_vert_example\n outline_painter\n unit_frame\n flip_vert\n\nconst flipped_outline_painter =\n flip_vert(outline_painter);\n\nflipped_outline_painter(unit_frame);\n```\n\nUsing\n\n```javascript\ntransform_painter,\n we can easily define new transformations.\n For example, we can declare a\n painter that shrinks its image to the upper-right quarter of the frame\n it is given:\n```\n\n```javascript\nshrink_to_upper_right\n transform_painter\n shrink_to_upper_right_example\n\nfunction shrink_to_upper_right(painter) {\n return transform_painter(painter,\n make_vect(0.5, 0.5),\n make_vect(1, 0.5),\n make_vect(0.5, 1));\n}\n```\n\n```javascript\nshrink_to_upper_right_example\n outline_painter\n unit_frame\n\nconst shrunk_outline_painter =\n shrink_to_upper_right(outline_painter);\n\nshrunk_outline_painter(unit_frame);\n```\n\nOther transformations rotate images counterclockwise by 90 degrees\n\n```javascript\nrotate90\n transform_painter\n rotate90_example\n\nfunction rotate90(painter) {\n return transform_painter(painter,\n make_vect(1, 0),\n make_vect(1, 1),\n make_vect(0, 0));\n}\n```\n\n```javascript\nrotate90_example\n outline_painter\n unit_frame\n\nconst rotated_outline_painter =\n rotate90(outline_painter);\n\nrotated_outline_painter(unit_frame);\n```\n\nor squash images towards the center of the frame:\n\n```javascript\nsquash_inwards\n transform_painter\n squash_inwards_example\n\nfunction squash_inwards(painter) {\n return transform_painter(painter,\n make_vect(0, 0),\n make_vect(0.65, 0.35),\n make_vect(0.35, 0.65));\n}\n```\n\n```javascript\nsquash_inwards_example\n outline_painter\n unit_frame\n\nconst squashed_outline_painter =\n squash_inwards(outline_painter);\n\nsquashed_outline_painter(unit_frame);\n```\n\nFrame transformation is also the key to\ndefining means of combining two or more painters.", + "token_count": 303, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Example: A Picture Language", + "chunk_index": 8, + "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_8" + }, + { + "content": "Frame transformation is also the key to\ndefining means of combining two or more painters.\n\nThe\nfunction,\nfor example, takes two painters, transforms them to paint in the left and\nright halves of an argument frame respectively, and produces a new,\ncompound painter.\n\nWhen the compound painter is given a frame, it calls the\nfirst transformed painter to paint in the left half of the frame and calls\nthe second transformed painter to paint in the right half of the frame:\n\n```javascript\nbeside\n transform_painter\n beside_example\n\nfunction beside(painter1, painter2) {\n const split_point = make_vect(0.5, 0);\n const paint_left = transform_painter(painter1,\n make_vect(0, 0),\n split_point,\n make_vect(0, 1));\n const paint_right = transform_painter(painter2,\n split_point,\n make_vect(1, 0),\n make_vect(0.5, 1));\n return frame => {\n paint_left(frame);\n paint_right(frame);\n };\n}\n```\n\n```javascript\nbeside_example\n x_painter_example\n\nbeside(x_painter, x_painter)(unit_frame);\n```\n\nObserve how the painter data abstraction, and in particular the representation of painters as functions, makes function need not know anything about the details of\n\nthe component painters other than that each painter will draw something in its designated frame.\n\nThe picture language exploits some of the critical ideas we ve\nintroduced about abstraction with\nfunctions\nand data.\n\nThe fundamental data abstractions, painters, are implemented\nusing\nfunctional\nrepresentations, which enables the language to handle different basic\ndrawing capabilities in a uniform way.\n\nThe means of combination satisfy\nthe closure property, which permits us to easily build up complex designs.\n\nFinally, all the tools for abstracting\nfunctions\nare available to us for abstracting means of combination for painters.\n\nWe have also obtained a glimpse of another crucial idea about languages and\nprogram design.\n\nThis is the approach of\nstratified design , the notion that a complex system should be\nstructured as a sequence of levels that are described using a sequence of\nlanguages.", "token_count": 289, - "source_files": [ - "subsection1.xml" - ] + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Example: A Picture Language", + "chunk_index": 9, + "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_9" }, { - "chapter_file": "chapter1_chunks.json", - "section": "The Elements of Programming", - "subsection": "Expressions", - "chunk_id": "chapter1_chunks_Expressions_5", + "content": "This is the approach of\nstratified design , the notion that a complex system should be\nstructured as a sequence of levels that are described using a sequence of\nlanguages.\n\nEach level is constructed by combining parts that are regarded\nas primitive at that level, and the parts constructed at each level are\nused as primitives at the next level.\n\nThe language used at each level\nof a stratified design has primitives, means of combination, and means\nof abstraction appropriate to that level of detail.\n\nStratified design pervades the engineering of complex systems.\n\nFor\nexample, in computer engineering, resistors and transistors are\ncombined (and described using a language of analog circuits) to\nproduce parts such as and-gates and or-gates, which form the\nprimitives of a language for digital-circuit design.\n\nAs a tiny example of stratification, our picture language uses primitive elements (primitive painters) that specify points and lines to provide the shapes of a\n\npainter like square_of_four, capture common patterns of combining geometric combiners.\n\nStratified design helps make programs\nrobust , that is, it makes\nit likely that small changes in a specification will require\ncorrespondingly small changes in the program.\n\nFor instance, suppose we\nwanted to change the image based on.\n\nWe could work\nat the lowest level to change the detailed appearance of the\ncorner_split\nreplicates the\nsquare_limit\narranges the four copies of the corner.\n\nIn general, each level of a\nstratified design provides a different vocabulary for expressing the\ncharacteristics of the system, and a different kind of ability to change it.", + "token_count": 255, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Example: A Picture Language", + "chunk_index": 10, + "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_10" + }, + { + "content": "The representation of sequences in terms of lists generalizes naturally to\nrepresent sequences whose elements may themselves be sequences.\n\nFor\nexample, we can regard the object\n[[1, [2, null]], [3, [4, null]]]\nconstructed by\n\n```javascript\npair(list(1, 2), list(3, 4));\n```\n\nas a list of three items, the first of which is itself a list,\n[1, [2, null]].\n\nFigure\nshows the representation of this structure in terms of pairs.\n\n```javascript\nStructure formed by\n\t pair(list(1, 2), list(3, 4)).\n```\n\nAnother way to think of sequences whose elements are sequences is as\ntrees.\n\nThe elements of the sequence are the branches of the\ntree, and elements that are themselves sequences are subtrees.\n\nFigure\nshows the structure in\nfigure\nviewed as a tree.\n\n```javascript\nThe list structure in\n\t figure viewed as a tree.\n```\n\nRecursion length function of section with the count_leaves function, which returns the total number of leaves of a tree:\n\n```javascript\ntree_x\n\nconst x = pair(list(1, 2), list(3, 4));\n```\n\n```javascript\nlength_tree_x\n tree_x\n 3\n\nlength(x);\n```\n\n```javascript\ncount_leaves_tree_x\n tree_x\n count_leaves\n 4\n\ncount_leaves(x);\n```\n\n```javascript\nlist_x_x\n tree_x\n 3\n\nlist(x, x);\n\nlength(head(tail(list(x, x))));\n```\n\n```javascript\nlength_list_x_x\n tree_x\n 2\n\nlength(list(x, x));\n```\n\n```javascript\ncount_leaves_list_x_x\n tree_x\n count_leaves\n 8\n\ncount_leaves(list(x, x));\n```\n\nTo implement count_leaves, recall the recursive plan for computing length: - - The length of a list the length of the tail of - -\n\nThe length of the empty list is 0.\n\n```javascript\nThe function\n count_leaves\n```\n\nis similar.\n\nThe value for the empty list is the same:\n-\n-\ncount_leaves\nof the empty list is 0.\n\nBut in the reduction step, where we strip off the\nhead\nof the list, we must take into account that the\nhead\nmay itself be a tree whose leaves we need to count.", + "token_count": 287, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Hierarchical Structures", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Data_Hierarchical_Structures_1" + }, + { + "content": "But in the reduction step, where we strip off the\nhead\nof the list, we must take into account that the\nhead\nmay itself be a tree whose leaves we need to count.\n\nThus, the appropriate\nreduction step is\n-\n-\ncount_leaves\nof a tree\ncount_leaves\nof the\nhead\nof\ncount_leaves\nof the\ntail\nof\nFinally, by taking\nheads\nwe reach actual leaves, so we need another base case:\n-\n-\ncount_leaves\nof a leaf is 1.\n\nTo aid in writing recursive\nfunctions\non trees,\nour JavaScript environment\nprovides the primitive predicate\nis_pair,\nwhich tests whether its argument is a pair.\n\nHere is the complete\nfunction:\n\n```javascript\ncount_leaves\n count_leaves_example\n 4\n\nfunction count_leaves(x) {\n return is_null(x)\n ? 0\n : ! is_pair(x)\n ? 1\n : count_leaves(head(x)) + count_leaves(tail(x));\n}\n```\n\n```javascript\ncount_leaves_example\n\ncount_leaves(pair(list(1, 2), list(3, 4)));\n```\n\nJust as\nscale_tree\nfunction,\nanalogous to\nscale_list\nof section , takes as arguments a numeric\nfactor and a tree whose leaves are numbers.\n\nIt returns a tree of the same\nshape, where each number is multiplied by the factor.\n\nThe recursive plan\nfor\nscale_tree\nis similar to the one for\ncount_leaves:\n\n```javascript\nscale_tree\n scale_tree_example\n 10\n\nfunction scale_tree(tree, factor) {\n return is_null(tree)\n ? null\n : ! is_pair(tree)\n ? tree * factor\n : pair(scale_tree(head(tree), factor),\n scale_tree(tail(tree), factor));\n}\n```\n\n```javascript\nscale_tree_example\n scale_tree\n\nscale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)),\n 10);\n\nhead(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)),\n 10));\n```\n\nAnother way to implement\nscale_tree\nis to regard the tree as a sequence of sub-trees and use\nmap.\n\nWe map over the sequence, scaling each sub-tree in turn, and return the\nlist of results.\n\nIn the base case, where the tree is a leaf, we simply\nmultiply by the factor:\n\n```javascript\nscale_tree_example_2\n\nscale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)),\n 10);\n\nhead(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)),\n 10));\n```", + "token_count": 301, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Hierarchical Structures", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Data_Hierarchical_Structures_2" + }, + { + "content": "In the base case, where the tree is a leaf, we simply\nmultiply by the factor:\n\n```javascript\nscale_tree_with_map\n scale_tree_example_2\n 10\n\nfunction scale_tree(tree, factor) {\n return map(sub_tree => is_pair(sub_tree)\n ? scale_tree(sub_tree, factor)\n : sub_tree * factor,\n tree);\n}\n```\n\nMany tree operations can be implemented by similar combinations of sequence operations and recursion.", + "token_count": 52, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Hierarchical Structures", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Data_Hierarchical_Structures_3" + }, + { + "content": "One of the useful structures we can build with pairs is a\nsequence an ordered collection of data objects.\n\nThere\nare, of course, many ways to represent sequences in terms of pairs.\n\nOne\nparticularly straightforward representation is illustrated in\nfigure,\nwhere the sequence 1, 2, 3, 4 is represented as a chain of pairs.\n\nThe\nhead\nof each pair is the\ncorresponding item in the chain, and the\ntail\nof the pair is the next pair in the chain.\n\nThe\ntail\nof the final pair signals the end of the\nsequence,\nrepresented in box-and-pointer\ndiagrams as a diagonal line\n\n```javascript\nJavaScripts primitive value\n\tnull.\n```\n\nThe entire sequence is constructed by nested pair operations:\n\n```javascript\ncons_example\n\npair(1,\n pair(2,\n pair(3,\n pair(4, null))));\n```\n\nSuch a sequence of pairs, formed by nested\npair applications,\nis called a\nlist , and\nour JavaScript environment\nprovides a primitive called\nlist(1, 2, 3, 4).\n\nIn general,\n\n```javascript\nlist(a$_{1}$, a$_{2}$, $\\ldots$, a$_{n}$)\n```\n\nis equivalent to\n\n```javascript\npair(a$_{1}$, pair(a$_{2}$, pair($\\ldots$, pair(a$_{n}$, null)$\\ldots$)))\n```\n\n```javascript\nOur interpreter prints pairs using a textual representation of\n\tbox-and-pointer diagrams that we call box notation.\n pair(1, 2)\n is printed as [1, 2], and\n\tthe data object in figure\n is printed as\n [1, [2, [3, [4, null]]]]:\n```\n\n```javascript\none_four\n one_four_example\n [ 2, [ 3, [ 4, null ] ] ]\n\nconst one_through_four = list(1, 2, 3, 4);\n```\n\n```javascript\none_four\n one_four_example\n\none_through_four;\n\ntail(one_through_four);\n```\n\nWe can think of\nhead\nas selecting the first item in the list, and of\ntail\nas selecting the sublist consisting of all but the first item.\n\nNested\napplications of\nhead\nand\ntail\ncan be used to extract the second, third, and subsequent items in the\nlist.\n\nThe constructor\npair\nmakes a list like the original one, but with an additional item at the\nbeginning.\n\n```javascript\ncar_one_four\n one_four\n 1\n\nhead(one_through_four);\n```", + "token_count": 304, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Representing Sequences", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Data_Representing_Sequences_1" + }, + { + "content": "The constructor\npair\nmakes a list like the original one, but with an additional item at the\nbeginning.\n\n```javascript\ncdr_one_four\n one_four\n [ 2, [ 3, [ 4, null ] ] ]\n\ntail(one_through_four);\n```\n\n```javascript\ncar_cdr_one_four\n one_four\n 2\n\nhead(tail(one_through_four));\n```\n\n```javascript\ncons_one_four\n one_four\n [ 2, [ 3, [ 4, null ] ] ]\n\npair(10, one_through_four);\n\ntail(tail(pair(10, one_through_four)));\n```\n\n```javascript\ncons5_one_four\n one_four\n [ 2, [ 3, [ 4, null ] ] ]\n\npair(5, one_through_four);\n\ntail(tail(pair(5, one_through_four)));\n```\n\n```javascript\nThe value null, used to terminate\n\tthe chain of pairs, can be thought of as a sequence of no elements, the\n empty list.\n```\n\nBox notation is sometimes difficult to read.\n\nIn this book, when we want to\nindicate the list nature of a data structure, we will employ the\nalternative\nlist notation : Whenever possible, list notation uses\napplications\nof\n\nwe write\n\nin list notation.\n\nThe use of pairs to represent sequences of elements as lists is accompanied\nby conventional programming techniques for manipulating lists by\nsuccessively\ntail to walk down the lists.\n\nFor example, the\nfunction\nlist_ref\ntakes as arguments a list and a number $n$ and\nreturns the $n$ th item of the list.\n\nIt is\ncustomary to number the elements of the list beginning with 0.\n\nThe method\nfor computing\nlist_ref\nis the following:\n-\n-\nFor $n=0$ ,\nlist_ref\nshould return the\nhead\nof the list.\n-\n-\nOtherwise,\nlist_ref\nshould return the $(n-1)$ st item of the\ntail\nof the list.\n\n```javascript\nlist_ref\n list_ref_example\n\nfunction list_ref(items, n) {\n return n === 0\n ? head(items)\n : list_ref(tail(items), n - 1);\n}\n```\n\n```javascript\nlist_ref_example\n list_ref\n 16\n\nconst squares = list(1, 4, 9, 16, 25);\n\nlist_ref(squares, 3);\n```\n\n```javascript\nmanual_squares\n\nconst squares = list(1, 4, 9, 16, 25);\n```\n\n```javascript\nmanual_odds\n\nconst odds = list(1, 3, 5, 7);\n```\n\nOften we\nwalk down the whole list.", + "token_count": 306, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Representing Sequences", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Data_Representing_Sequences_2" + }, + { + "content": "Often we\nwalk down the whole list.\n\nTo aid in this,\nour JavaScript environment\nincludes a primitive\npredicate\nis_null,\nwhich tests whether its argument is the empty list.\n\nThe\nfunction\n\n```javascript\nlength\n length_example\n 4\n\nfunction length(items) {\n return is_null(items)\n ? 0\n : 1 + length(tail(items));\n}\n```\n\n```javascript\nlength_example\n\nconst odds = list(1, 3, 5, 7);\n\nlength(odds);\n```\n\nThe function\nimplements a simple recursive plan.\n\nThe reduction step is:\n-\n-\nThe\ntail\nof the list.\n\nThis is applied successively until we reach the base case:\n-\n-\nThe\nWe could also compute\n\n```javascript\nlength_iter\n length_example\n 4\n\nfunction length(items) {\n function length_iter(a, count) {\n return is_null(a)\n ? count\n : length_iter(tail(a), count + 1);\n }\n return length_iter(items, 0);\n}\n```\n\nAnother conventional programming technique is to tail, as in the function\n\n```javascript\nappend_example\n append\n manual_squares\n manual_odds\n 9\n\nappend(squares, odds);\n\nlength(append(squares, odds));\n```\n\n```javascript\nappend_example2\n append\n manual_squares\n manual_odds\n 9\n\nappend(odds, squares);\n\nlength(append(odds, squares));\n```\n\nThe function append\nis also implemented using a recursive plan.\n\nTo\n-\n-\nIf\n-\n-\nOtherwise,\ntail\nof\nadjoin\nthe\nhead\nof\nto the result:\n\n```javascript\nappend\n append_example\n 9\n\nfunction append(list1, list2) {\n return is_null(list1)\n ? list2\n : pair(head(list1), append(tail(list1), list2));\n}\n```\n\nWe want to rewrite the function\n\n```javascript\nus_coins\n\nconst us_coins = list(50, 25, 10, 5, 1);\nconst uk_coins = list(100, 50, 20, 10, 5, 2, 1);\n```\n\nWe could then call\n\n```javascript\ncc_example\n cc\n us_coins\n\ncc(100, us_coins);\n```\n\nTo do this will require changing the program\n\n```javascript\ncc_helpers\n\n// first_denomination, except_first_denomination\n// and no_more to be given by student\n```\n\n```javascript\ncc\n cc_helpers\n\tcc_example\n\nfunction cc(amount, coin_values) {\n return amount === 0\n ? 1\n : amount < 0 || no_more(coin_values)\n ? 0\n : cc(amount, except_first_denomination(coin_values)) +\n cc(amount - first_denomination(coin_values), coin_values);\n}\n```\n\nDefine the\nfunctions\nfirst_denomination,\nexcept_first_denomination,\nand\nno_more\nin terms of primitive operations on list structures.\n\nDoes the order of\nthe list\ncoin_values\naffect the answer produced by", + "token_count": 315, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Representing Sequences", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Data_Representing_Sequences_3" + }, + { + "content": "Does the order of\nthe list\ncoin_values\naffect the answer produced by\n\nOne extremely useful operation is to apply some transformation to each\nelement in a list and generate the list of results.\n\nFor instance, the\nfollowing\nfunction\nscales each number in a list by a given factor:\n\n```javascript\nscale_list\n scale_list_example\n [ 30, [ 40, [ 50, null ] ] ]\n\nfunction scale_list(items, factor) {\n return is_null(items)\n ? null\n : pair(head(items) * factor,\n scale_list(tail(items), factor));\n}\n```\n\n```javascript\nscale_list_example\n\nscale_list(list(1, 2, 3, 4, 5), 10);\n\ntail(tail(scale_list(list(1, 2, 3, 4, 5), 10)));\n```\n\nWe can abstract this general idea and capture it as a common pattern\nexpressed as a higher-order\nfunction,\njust as in section.\n\nThe\nhigher-order\nfunction\nhere is called\nThe function map\ntakes as arguments a\nfunction\nof one argument and a list, and returns a list of the results produced by\napplying the\nfunction\nto each element in the list:\n\n```javascript\nmap\n map_example\n [ 2.5, [ 11.6, [ 17, null ] ] ]\n\nfunction map(fun, items) {\n return is_null(items)\n ? null\n : pair(fun(head(items)),\n map(fun, tail(items)));\n}\n```\n\n```javascript\nmap_example\n abs_definition\n map\n [ 2.5, [ 11.6, [ 17, null ] ] ]\n\nmap(abs, list(-10, 2.5, -11.6, 17));\n\ntail(map(abs, list(-10, 2.5, -11.6, 17)));\n```\n\n```javascript\nmap_example2\n map\n [ 4, [ 9, [ 16, null ] ] ]\n\nmap(x => x * x, list(1, 2, 3, 4));\n\ntail(map(x => x * x, list(1, 2, 3, 4)));\n```\n\nNow we can give a new definition of scale_list in terms of\n\n```javascript\nscale_list2\n scale_list_example\n [ 30, [ 40, [ 50, null ] ] ]\n\nfunction scale_list(items, factor) {\n return map(x => x * factor, items);\n}\n```\n\nThe function map\nis an important construct, not only because it captures a common pattern,\nbut because it establishes a higher level of abstraction in dealing with\nlists.", + "token_count": 302, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Representing Sequences", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Data_Representing_Sequences_4" + }, + { + "content": "The function map\nis an important construct, not only because it captures a common pattern,\nbut because it establishes a higher level of abstraction in dealing with\nlists.\n\nIn the original definition of\nscale_list,\nthe recursive structure of the program draws attention to the\nelement-by-element processing of the list.\n\nDefining\nscale_list\nin terms of t) but that we\nthink about the process differently.\n\nIn effect,\nfunctions\nthat transform lists from the details of how the elements of the list are\nextracted and combined.\n\nLike the barriers shown in\nfigure,\nthis abstraction gives us the flexibility to change the low-level details\nof how sequences are implemented, while preserving the conceptual framework\nof operations that transform sequences to sequences.\n\nSection expands\non this use of sequences as a framework for organizing programs.\n\nLouis then tries to fix his bug by interchanging the arguments to pair:\n\n```javascript\nsquare_list3\n square_definition\n square_list_warning\n square_list_example\n 16\n\nfunction square_list(items) {\n function iter(things, answer) {\n return is_null(things)\n ? answer\n : iter(tail(things),\n pair(answer,\n square(head(things))));\n }\n return iter(items, null);\n}\n```\n\nThis doesn t work either.\n\nExplain.", + "token_count": 176, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Representing Sequences", "chunk_index": 5, - "content": "This mode of operation is often expressed by saying that the interpreter runs in a read-eval-print loop . read-evaluate-print loop . Observe in particular that it is not necessary to explicitly instruct the interpreter to print the value of the expression. statement.", - "token_count": 51, - "source_files": [ - "subsection1.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Representing_Sequences_5" }, { - "chapter_file": "chapter2_chunks.json", - "section": null, - "subsection": "Building Abstractions with Data", - "chunk_id": "chapter2_chunks_Building_Abstractions_with_Data_1", + "content": "In working with compound data, we ve stressed how data abstraction\npermits us to design programs without becoming enmeshed in the details\nof data representations, and how abstraction preserves for us the\nflexibility to experiment with alternative representations.\n\nIn this\nsection, we introduce another powerful design principle for working\nwith data structures the use of conventional interfaces.\n\nIn section we saw how\nprogram abstractions, implemented as higher-order\nfunctions,\ncan capture common patterns in programs that deal with numerical data.\n\nOur\nability to formulate analogous operations for working with compound data\ndepends crucially on the style in which we manipulate our data structures.\n\nConsider, for example, the following\nfunction,\nanalogous to the\ncount_leaves\nfunction\nof section , which takes a tree as argument\nand computes the sum of the squares of the leaves that are odd:\n\n```javascript\nodd_definition\n\nfunction is_odd(n) {\n return n % 2 === 1;\n}\n```\n\n```javascript\nsquare_definition\n odd_definition\n sum_odd_squares_example\n 34\n\nfunction sum_odd_squares(tree) {\n return is_null(tree)\n ? 0\n : ! is_pair(tree)\n ? is_odd(tree) ? square(tree) : 0\n : sum_odd_squares(head(tree)) +\n sum_odd_squares(tail(tree));\n}\n```\n\n```javascript\nsum_odd_squares_example\n\nsum_odd_squares(list(list(2, 3), list(4, 5)));\n```\n\nOn the surface, this function is very different from the following one, which constructs a list of all the even Fibonacci numbers ${\\textrm{Fib}}(k)$ , where\n\n$k$ is less than or equal to a given integer $n$ :\n\n```javascript\neven_definition\n fib_definition\n even_fibs_example\n [ 2, [ 8, [ 34, null ] ] ]\n\nfunction even_fibs(n) {\n function next(k) {\n if (k > n) {\n return null;\n } else {\n const f = fib(k);\n return is_even(f)\n ? pair(f, next(k + 1))\n : next(k + 1);\n }\n }\n return next(0);\n}\n```\n\n```javascript\neven_fibs_example\n\neven_fibs(9);\n\ntail(even_fibs(9));\n```\n\nDespite the fact that these two\nfunctions\nare structurally very different, a more abstract description of the two\ncomputations reveals a great deal of similarity.", + "token_count": 300, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Sequences as Conventional Interfaces", "chunk_index": 1, - "content": "We concentrated in chapter on computational processes and on the role of procedures functions in program design. We saw how to use primitive data (numbers) and primitive operations (arithmetic operations), how to combine procedures functions to form compound procedures functions through composition, conditionals, and the use of parameters, and how to abstract procedures processes by using define . function declarations. We saw that a procedure function can be regarded as a pattern for the local evolution of a process, and we classified, reasoned about, and performed simple algorithmic analyses of some common patterns for processes as embodied in procedures. functions. We also saw that higher-order procedures 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. In this chapter we are going to look at more complex data. All the procedures functions in chapter operate on simple numerical data, and simple data are not sufficient for many of the problems we wish to address using computation. Programs are typically designed to model complex phenomena, and more often than not one must construct computational objects that have several parts in order to model real-world phenomena that have several aspects. Thus, whereas our focus in chapter was on building abstractions by combining procedures functions to form compound procedures, functions, we turn in this chapter to another key aspect of any programming language: the means it provides for building abstractions by combining data objects to form compound data .", - "token_count": 291, - "source_files": [ - "chapter2.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_1" }, { - "chapter_file": "chapter2_chunks.json", - "section": null, - "subsection": "Building Abstractions with Data", - "chunk_id": "chapter2_chunks_Building_Abstractions_with_Data_2", + "content": "Despite the fact that these two\nfunctions\nare structurally very different, a more abstract description of the two\ncomputations reveals a great deal of similarity.\n\nThe first program\n-\n-\nenumerates the leaves of a tree;\n-\n-\nfilters them, selecting the odd ones;\n-\n-\nsquares each of the selected ones; and\n-\n-\naccumulates the results using\n+,\nstarting with 0.\n\nThe second program\n-\n-\nenumerates the integers from 0 to $n$ ;\n-\n-\ncomputes the Fibonacci number for each integer;\n-\n-\nfilters them, selecting the even ones; and\n-\n-\naccumulates the results using\npair,\nstarting with the empty list.\n\nA signal-processing engineer would find it natural to conceptualize these\nprocesses in terms of\nfigure.\n\nIn\nsum_odd_squares,\nwe begin with an\nenumerator , which generates a signal consisting of\nthe leaves of a given tree.\n\nThis signal is passed through a\nfilter , which eliminates all but the odd elements.\n\nThe resulting\nsignal is in turn passed through a\nmap , which is a transducer that applies the\nfunction\nto each element.\n\nThe output of the map is then fed to an\naccumulator , which combines the elements using\n+,\nstarting from an initial 0.\n\nThe plan for\neven_fibs\nis analogous.\n\nUnfortunately, the two\nfunction declarations\nabove fail to exhibit this signal-flow structure.\n\nFor instance, if we\nexamine the\nsum_odd_squares\nfunction,\nwe find that the enumeration is implemented partly by the\nis_null\nand\nis_pair\ntests and partly by the tree-recursive structure of the\nfunction.\n\nSimilarly, the accumulation is found partly in the tests and partly in the\naddition used in the recursion.\n\nIn general, there are no distinct parts of\neither\nfunction\nthat correspond to the elements in the signal-flow description.", + "token_count": 287, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Sequences as Conventional Interfaces", "chunk_index": 2, - "content": "Why do we want compound data in a programming language? For the same reasons that we want compound procedures: functions: to elevate the conceptual level at which we can design our programs, to increase the modularity of our designs, and to enhance the expressive power of our language. Just as the ability to define procedures declare functions enables us to deal with processes at a higher conceptual level than that of the primitive operations of the language, the ability to construct compound data objects enables us to deal with data at a higher conceptual level than that of the primitive data objects of the language. Consider the task of designing a system to perform add-rat add_rat that takes two rational numbers and produces their sum. In terms of simple data, a rational number can be thought of as two integers: a numerator and a denominator. Thus, we could design a program in which each rational number would be represented by two integers (a numerator and a denominator) and where add-rat add_rat would be implemented by two procedures functions (one producing the numerator of the sum and one producing the denominator). But this would be awkward, because we would then need to explicitly keep track of which numerators corresponded to which denominators. In a system intended to perform many operations on many rational numbers, such bookkeeping details would clutter the programs substantially, to say nothing of what they would do to our minds.", - "token_count": 275, - "source_files": [ - "chapter2.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_2" }, { - "chapter_file": "chapter2_chunks.json", - "section": null, - "subsection": "Building Abstractions with Data", - "chunk_id": "chapter2_chunks_Building_Abstractions_with_Data_3", + "content": "In general, there are no distinct parts of\neither\nfunction\nthat correspond to the elements in the signal-flow description.\n\nOur two\nfunctions\ndecompose the computations in a different way, spreading the enumeration\nover the program and mingling it with the map, the filter, and the\naccumulation.\n\nIf we could organize our programs to make the signal-flow\nstructure manifest in the\nfunctions\nwe write, this would increase the conceptual clarity of the resulting\nprogram.\n\nThe key to organizing programs so as to more clearly reflect the\nsignal-flow structure is to concentrate on the signals that\nflow from one stage in the process to the next.\n\nIf we represent these\nsignals as lists, then we can use list operations to implement the\nprocessing at each of the stages.\n\nFor instance, we can implement the\nmapping stages of the signal-flow diagrams using the\nfunction\nfrom section :\n\n```javascript\nsquare_definition\n map\n 25\n\nmap(square, list(1, 2, 3, 4, 5));\n\nlist_ref(map(square, list(1, 2, 3, 4, 5)), 4);\n```\n\nFiltering a sequence to select only those elements that satisfy a given predicate is accomplished by\n\n```javascript\nfilter\n filter_odd\n [ 1, [ 3, [ 5, null ] ] ]\n\nfunction filter(predicate, sequence) {\n return is_null(sequence)\n ? null\n : predicate(head(sequence))\n ? pair(head(sequence),\n filter(predicate, tail(sequence)))\n : filter(predicate, tail(sequence));\n}\n```\n\n```javascript\nfilter_odd\n [ 1, [ 3, [ 5, null ] ] ]\n\nfunction is_odd(n) {\n return n % 2 === 1;\n}\nfilter(is_odd, list(1, 2, 3, 4, 5));\n```\n\nFor example,\n\n```javascript\nfilter\n odd_definition\n\nfilter(is_odd, list(1, 2, 3, 4, 5));\n```\n\nAccumulations can be implemented by\n\n```javascript\naccumulate\n\nfunction accumulate(op, initial, sequence) {\n return is_null(sequence)\n ? initial\n : op(head(sequence),\n accumulate(op, initial, tail(sequence)));\n}\n```\n\n```javascript\nsimple_plus\n\nfunction plus(x, y) {\n return x + y;\n}\n```\n\n```javascript\naccumulate\n simple_plus\n 15\n\naccumulate(plus, 0, list(1, 2, 3, 4, 5));\n```\n\n```javascript\nsimple_times\n\nfunction times(x, y) {\n return x * y;\n}\n```", + "token_count": 310, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Sequences as Conventional Interfaces", "chunk_index": 3, - "content": "It would be much better if we could glue together a numerator and denominator to form a pair a compound data object that our programs could manipulate in a way that would be consistent with regarding a rational number as a single conceptual unit. The use of compound data also enables us to increase the modularity of our programs. If we can manipulate rational numbers directly as objects in their own right, then we can separate the part of our program that deals with rational numbers per se from the details of how rational numbers may be represented as pairs of integers. The general technique of isolating the parts of a program that deal with how data objects are represented from the parts of a program that deal with how data objects are used is a powerful design methodology called data abstraction . We will see how data abstraction makes programs much easier to design, maintain, and modify. The use of compound data leads to a real increase in the expressive power of our programming language. Consider the idea of forming a linear combination $ax+by$ . We might like to write a procedure function that would accept $a$ , $b$ , $x$ , and $y$ as arguments and return the value of $ax+by$ .", - "token_count": 240, - "source_files": [ - "chapter2.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_3" }, { - "chapter_file": "chapter2_chunks.json", - "section": null, - "subsection": "Building Abstractions with Data", - "chunk_id": "chapter2_chunks_Building_Abstractions_with_Data_4", + "content": "Accumulations can be implemented by\n\n```javascript\naccumulate\n simple_times\n 120\n\naccumulate(times, 1, list(1, 2, 3, 4, 5));\n```\n\n```javascript\naccumulate\n [ 3, [ 4, [ 5, null ] ] ]\n\naccumulate(pair, null, list(1, 2, 3, 4, 5));\n\ntail(tail(accumulate(pair, null, list(1, 2, 3, 4, 5))));\n```\n\nAll that remains to implement signal-flow diagrams is to enumerate the\nsequence of elements to be processed.\n\nFor\neven_fibs,\nwe need to generate the sequence of integers in a given range, which we\ncan do as follows:\n\n```javascript\nenumerate_interval\n enumerate_interval_example\n [ 5, [ 6, [ 7, null ] ] ]\n\nfunction enumerate_interval(low, high) {\n return low > high\n ? null\n : pair(low,\n enumerate_interval(low + 1, high));\n}\n```\n\n```javascript\nenumerate_interval_example\n enumerate_interval\n\nenumerate_interval(2, 7);\n\ntail(tail(tail(enumerate_interval(2, 7))));\n```\n\nTo enumerate the leaves of a tree, we can use\n\n```javascript\nenumerate_tree\n enumerate_tree_example\n [ 3, [ 4, [ 5, null ] ] ]\n\nfunction enumerate_tree(tree) {\n return is_null(tree)\n ? null\n : ! is_pair(tree)\n ? list(tree)\n : append(enumerate_tree(head(tree)),\n enumerate_tree(tail(tree)));\n}\n```\n\n```javascript\nenumerate_tree_example\n enumerate_tree\n\nenumerate_tree(list(1, list(2, list(3, 4)), 5));\n\ntail(tail(enumerate_tree(list(1, list(2, list(3, 4)), 5))));\n```\n\nNow we can reformulate\nsum_odd_squares\nand\neven_fibs\nas in the signal-flow diagrams.\n\nFor\nsum_odd_squares,\nwe enumerate the sequence of leaves of the tree, filter this to keep only\nthe odd numbers in the sequence, square each element, and sum the results:\n\n```javascript\nsquare_definition\n simple_plus\n odd_definition\n enumerate_tree\n sum_odd_squares_example\n 34\n\nfunction sum_odd_squares(tree) {\n return accumulate(plus,\n 0,\n map(square,\n filter(is_odd,\n enumerate_tree(tree))));\n}\n```\n\nFor even_fibs, we enumerate the integers from 0 to $n$ , generate the Fibonacci number for each of these integers, filter the resulting sequence to\n\nkeep only the even elements, and accumulate the results into a list:\n\n```javascript\neven_definition\n fib_definition\n enumerate_interval\n even_fibs_example\n [ 2, [ 8, [ 34, null ] ] ]\n\nfunction even_fibs(n) {\n return accumulate(pair,\n null,\n filter(is_even,\n map(fib,\n enumerate_interval(0, n))));\n}\n```", + "token_count": 297, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Sequences as Conventional Interfaces", "chunk_index": 4, - "content": "This presents no difficulty if the arguments are to be numbers, because we can readily define the procedure declare the function (define (linear-combination a b x y) (+ (* a x) (* b y))) function linear_combination(a, b, x, y) { return a * x + b * y; } linear_combination(1, 2, 3, 4); But suppose we are not concerned only with numbers. Suppose we would like to express, in procedural terms, the idea that one can form describe a process that forms linear combinations whenever addition and multiplication are defined for rational numbers, complex numbers, polynomials, or whatever. We could express this as a procedure function of the form (define (linear-combination a b x y) (add (mul a x) (mul b y))) function linear_combination(a, b, x, y) { return add(mul(a, x), mul(b, y)); } where add and mul are not the primitive procedures functions + and * but rather more complex things that will perform the appropriate operations for whatever kinds of data we pass in as the arguments a , b , x , and y . The key point is that the only thing linear-combination linear_combination should need to know about a , b , x , and y is that the procedures functions add and mul will perform the appropriate manipulations.", - "token_count": 285, - "source_files": [ - "chapter2.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_4" }, { - "chapter_file": "chapter2_chunks.json", - "section": null, - "subsection": "Building Abstractions with Data", - "chunk_id": "chapter2_chunks_Building_Abstractions_with_Data_5", + "content": "keep only the even elements, and accumulate the results into a list:\n\nThe value of expressing programs as sequence operations is that this\nhelps us make program designs that are modular, that is, designs that\nare constructed by combining relatively independent pieces.\n\nWe can\nencourage modular design by providing a library of standard components\ntogether with a conventional interface for connecting the components\nin flexible ways.\n\nModular construction sum_odd_squares and even_fibs functions in a program that constructs a list of the squares of the first $n+1$ Fibonacci numbers:\n\n```javascript\nlist_fib_squares\n square_definition\n fib_definition\n enumerate_interval\n list_fib_squares_example\n 11\n\nfunction list_fib_squares(n) {\n return accumulate(pair,\n null,\n map(square,\n map(fib,\n enumerate_interval(0, n))));\n}\n```\n\n```javascript\nlist_fib_squares_example\n list_fib_squares\n\nlist_fib_squares(10);\n\nlength(list_fib_squares(10));\n```\n\nWe can rearrange the pieces and use them in computing the product of the squares of the odd integers in a sequence:\n\n```javascript\nproduct_of_squares_of_odd_elements\n square_definition\n odd_definition\n simple_times\n product_of_squares_of_odd_elements_example\n 225\n\nfunction product_of_squares_of_odd_elements(sequence) {\n return accumulate(times,\n 1,\n map(square,\n filter(is_odd, sequence)));\n}\n```\n\n```javascript\nproduct_of_squares_of_odd_elements_example\n product_of_squares_of_odd_elements\n\nproduct_of_squares_of_odd_elements(list(1, 2, 3, 4, 5));\n```\n\nWe can also formulate conventional data-processing applications in terms of\nsequence operations.\n\nSuppose we have a sequence of personnel records and\nwe want to find the salary of the highest-paid programmer.\n\nAssume that we\nhave a selector\nis_programmer\nthat tests if a record is for a programmer.\n\nThen we can write\n\n```javascript\nlinus\n\nconst my_records = list(list(\"Linus\", \"programmer\", 30000),\n list(\"Richard\", \"programmer\", 25000),\n list(\"Bill\", \"manager\", 2500000));\nfunction is_programmer(record) {\n return head(tail(record)) === \"programmer\";\n}\nfunction salary(record) {\n return head(tail(tail(record)));\n}\nsalary_of_highest_paid_programmer(my_records);\n```\n\n```javascript\nlinus\n 30000\n\nfunction salary_of_highest_paid_programmer(records) {\n return accumulate(math_max,\n 0,\n map(salary,\n filter(is_programmer, records)));\n}\n```\n\nThese examples give just a hint of the vast range of operations that can be expressed as sequence operations.\n\nSequences, implemented here as lists, serve as a conventional interface\nthat permits us to combine processing modules.", + "token_count": 293, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Sequences as Conventional Interfaces", "chunk_index": 5, - "content": "From the perspective of the procedure function linear-combination , linear_combination , it is irrelevant what a , b , x , and y are and even more irrelevant how they might happen to be represented in terms of more primitive data. This same example shows why it is important that our programming language provide the ability to manipulate compound objects directly: Without this, there is no way for a procedure function such as linear-combination linear_combination to pass its arguments along to add and mul without having to know their detailed structure. We begin this chapter by implementing the rational-number arithmetic system mentioned above. This will form the background for our discussion of compound data and data abstraction. As with compound procedures, functions, the main issue to be addressed is that of abstraction as a technique for coping with complexity, and we will see how data abstraction enables us to erect suitable abstraction barriers between different parts of a program. We will see that the key to forming compound data is that a programming language should provide some kind of glue so that data objects can be combined to form more complex data objects. There are many possible kinds of glue. Indeed, we will discover how to form compound data using no special data operations at all, only procedures. functions. This will further blur the distinction between procedure function and data, which was already becoming tenuous toward the end of chapter . We will also explore some conventional techniques for representing sequences and trees.", + "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_5" + }, + { + "content": "Sequences, implemented here as lists, serve as a conventional interface\nthat permits us to combine processing modules.\n\nAdditionally, when we\nuniformly represent structures as sequences, we have localized the\ndata-structure dependencies in our programs to a small number of sequence\noperations.\n\nBy changing these, we can experiment with alternative\nrepresentations of sequences, while leaving the overall design of our\nprograms intact.\n\nWe will exploit this capability in\nsection , when we generalize the\nsequence-processing paradigm to admit infinite sequences.\n\nWe can extend the sequence paradigm to include many computations that are\ncommonly expressed using nested loops. $n$ , find all ordered pairs of distinct positive\nintegers $i$ and $j$ ,\nwhere $1\\leq j < i\\leq n$ , such that\n$i +j$ is prime.\n\nFor example, if\n$n$ is 6, then the pairs are the following:\n\\[\n\\begin{array}{c|ccccccc}\ni & 2 & 3 & 4 & 4 & 5 & 6 & 6 \\\\\nj & 1 & 2 & 1 & 3 & 2 & 1 & 5 \\\\\n\\hline\ni+j & 3 & 5 & 5 & 7 & 7 & 7 & 11\n\\end{array}\n\\]\nA natural way to organize this computation is to generate the sequence\nof all ordered pairs of positive integers less than or equal to\n$n$ , filter to select those pairs whose sum is\nprime, and then, for each pair $(i, j)$ that\npasses through the filter, produce the triple\n$(i, j, i+j)$.\n\nHere is a way to generate the sequence of pairs: For each integer\n$i\\leq n$ , enumerate the integers\n$j < i$ , and for each such\n$i$ and $j$\ngenerate the pair $(i, j)$.\n\nIn terms of\nsequence operations, we map along the sequence\nenumerate_interval(1, n).", + "token_count": 287, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Sequences as Conventional Interfaces", + "chunk_index": 6, + "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_6" + }, + { + "content": "In terms of\nsequence operations, we map along the sequence\nenumerate_interval(1, n).\n\nFor each $i$ in this sequence, we map along the\nsequence\nenumerate_interval(1, i - 1).\n\nFor each $j$ in this latter sequence, we\ngenerate the pair\nlist(i, j).\n\nThis gives us a sequence of pairs for each $i$.\n\nCombining all the sequences for all the $i$ (by\naccumulating with\n\n```javascript\nenumerate_interval_n\n\nconst n = 6;\n```\n\n```javascript\nenumerate_interval\n enumerate_interval_n\n 15\n\naccumulate(append,\n null,\n map(i => map(j => list(i, j),\n enumerate_interval(1, i - 1)),\n enumerate_interval(1, n)));\n\nlength(accumulate(append,\n null,\n map(i => map(j => list(i, j),\n enumerate_interval(1, i - 1)),\n enumerate_interval(1, n))));\n```\n\nThe combination of mapping and accumulating with function:\n\n```javascript\nflatmap\n flatmap_example\n 8\n\nfunction flatmap(f, seq) {\n return accumulate(append, null, map(f, seq));\n}\n```\n\n```javascript\nflatmap_example\n\nflatmap(x => list(x, x), list(1, 2, 3, 4));\n\nlength(flatmap(x => list(x, x), list(1, 2, 3, 4)));\n```\n\nNow filter this sequence of pairs to find those whose sum is prime.\n\nThe\nfilter predicate is called for each element of the sequence; its argument\nis a pair and it must extract the integers from the pair.\n\nThus, the\npredicate to apply to each element in the sequence is\n\n```javascript\nprime_sum\n prime_definition\n prime_sum_example\n true\n\nfunction is_prime_sum(pair) {\n return is_prime(head(pair) + head(tail(pair)));\n}\n```\n\n```javascript\nprime_sum_example\n\nis_prime_sum(list(8, 9));\n```\n\nFinally, generate the sequence of results by mapping over the filtered pairs using the following function, which constructs a triple consisting of the two elements\n\nof the pair along with their sum:\n\n```javascript\nmake_pair_sum\n make_pair_sum_example\n [ 8, [ 9, [ 17, null ] ] ]\n\nfunction make_pair_sum(pair) {\n return list(head(pair), head(tail(pair)),\n head(pair) + head(tail(pair)));\n}\n```\n\n```javascript\nmake_pair_sum_example\n\nmake_pair_sum(list(8, 9));\n```\n\nCombining all these steps yields the complete function:\n\n```javascript\nprime_sum_pairs\n make_pair_sum\n prime_sum\n flatmap\n enumerate_interval\n prime_sum_pairs_example\n 7\n\nfunction prime_sum_pairs(n) {\n return map(make_pair_sum,\n filter(is_prime_sum,\n flatmap(i => map(j => list(i, j),\n enumerate_interval(1, i - 1)),\n enumerate_interval(1, n))));\n}\n```\n\n```javascript\nprime_sum_pairs_example\n\nprime_sum_pairs(6);\n\nlength(prime_sum_pairs(6));\n```", + "token_count": 314, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Sequences as Conventional Interfaces", + "chunk_index": 7, + "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_7" + }, + { + "content": "Combining all these steps yields the complete function:\n\nNested mappings are also useful for sequences other than those that\nenumerate intervals.\n\nSuppose we wish to generate all the\n$S$ ; that is, all the ways of ordering\nthe items in the set.\n\nFor instance, the permutations of\n$\\{1, 2, 3\\}$ are\n$\\{1, 2, 3\\}$ ,\n$\\{ 1, 3, 2\\}$ ,\n$\\{2, 1, 3\\}$ ,\n$\\{ 2, 3, 1\\}$ ,\n$\\{ 3, 1, 2\\}$ , and\n$\\{ 3, 2, 1\\}$.\n\nHere is a plan for generating\nthe permutations of $S$ : For each item\n$x$ in $S$ ,\nrecursively generate the sequence of permutations of\n$S-x$ , $x$ to the front of each one.\n\nThis yields, for\neach $x$ in $S$ , the\nsequence of permutations of $S$ that begin\nwith $x$.\n\nCombining these sequences for\nall $x$ gives all the permutations\nof $S$ :\n\n```javascript\nflatmap\n permutations_example\n 6\n\nfunction permutations(s) {\n return is_null(s) // empty set?\n ? list(null) // sequence containing empty set\n : flatmap(x => map(p => pair(x, p),\n permutations(remove(x, s))),\n s);\n}\n```\n\n```javascript\npermutations_example\n\npermutations(list(1, 2, 3));\n\nlength(permutations(list(1, 2, 3)));\n```\n\nNotice how this strategy reduces the problem of generating permutations of\n$S$ to the problem of generating the\npermutations of sets with fewer elements than\n$S$.\n\nIn the terminal case, we work our way down\nto the empty list, which represents a set of no elements.\n\nFor this, we\ngenerate\nlist(null),\nwhich is a sequence with one item, namely the set with no elements.\n\nThe\nremove\nfunction\nused in\n\n```javascript\nremove\n remove_example\n 4\n\nfunction remove(item, sequence) {\n return filter(x => ! (x === item),\n sequence);\n}\n```\n\n```javascript\nremove_example\n\nlength(remove(3, list(1, 2, 3, 4, 5)));\n\nlength(remove(3, list(1, 2, 3, 4, 5)));\n```", + "token_count": 286, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Sequences as Conventional Interfaces", + "chunk_index": 8, + "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_8" + }, + { + "content": "The\nremove\nfunction\nused in\n\nWe implement this solution as a\nfunction\nqueens,\nwhich returns a sequence of all solutions to the problem of placing\n$n$ queens on an\n$n\\times n$ chessboard.\n\nThe function queens\nhas an internal\nfunction\nqueens_cols\nthat returns the sequence of all ways to place queens in the first\n$k$ columns of the board.\n\n```javascript\nexample_queens\n\nqueens(8);\n\nlength(queens(8));\n```\n\n```javascript\nqueens\n flatmap\n enumerate_interval\n example_queens\n\nfunction queens(board_size) {\n function queen_cols(k) {\n return k === 0\n ? list(empty_board)\n : filter(positions => is_safe(k, positions),\n flatmap(rest_of_queens =>\n map(new_row =>\n adjoin_position(new_row, k,\n rest_of_queens),\n enumerate_interval(1, board_size)),\n queen_cols(k - 1)));\n }\n return queen_cols(board_size);\n}\n```\n\nIn this\nfunction\nrest_of_queens\nis a way to place $k-1$ queens in the first\n$k-1$ columns, and\nnew_row\nis a proposed row in which to place the queen for the\n$k$ th column.\n\nComplete the program by\nimplementing the representation for sets of board positions, including the\nfunction\nadjoin_position,\nwhich adjoins a new row-column position to a set of positions, and\nempty_board,\nwhich represents an empty set of positions.\n\nYou must also write the\nfunction\nis_safe,\nwhich determines for a set of positions whether the queen in the\n$k$ th column is safe with respect to the others.\n\n(Note that we need only check whether the new queen is safe the\nother queens are already guaranteed safe with respect to each other.)", + "token_count": 224, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Sequences as Conventional Interfaces", + "chunk_index": 9, + "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_9" + }, + { + "content": "In section , we noted that a\nfunction\nused as an element in creating a more complex\nfunction\ncould be regarded not only as a collection of particular operations but\nalso as a\nfunctional\nabstraction.\n\nThat is, the details of how the\nfunction\nwas implemented could be suppressed, and the particular\nfunction\nitself could be replaced by any other\nfunction\nwith the same overall behavior.\n\nIn other words, we could make an\nabstraction that would separate the way the\nfunction\nwould be used from the details of how the\nfunction\nwould be implemented in terms of more primitive\nfunctions.\n\nThe analogous notion for compound data is called\ndata abstraction.\n\nData abstraction is a methodology that enables\nus to isolate how a compound data object is used from the details of how it\nis constructed from more primitive data objects.\n\nThe basic idea of data abstraction is to structure the programs that are\nto use compound data objects so that they operate on\nabstract data.\n\nThat is, our programs should use data in such\na way as to make no assumptions about the data that are not strictly\nnecessary for performing the task at hand.\n\nAt the same time, a\nconcrete data representation is defined independent of the\nprograms that use the data.\n\nThe interface between these two parts of our\nsystem will be a set of\nfunctions,\ncalled\nselectors and\nconstructors , that implement the abstract data in terms of the\nconcrete representation.\n\nTo illustrate this technique, we will consider\nhow to design a set of\nfunctions\nfor manipulating rational numbers.", + "token_count": 261, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Introduction to Data Abstraction", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Data_Introduction_to_Data_Abstraction_1" + }, + { + "content": "make_@rat and selectors\n\nWe can envision the structure of the rational-number system as\nshown in\nfigure.\n\nThe horizontal lines represent abstraction barriers that isolate\ndifferent levels of the system.\n\nAt each level, the barrier\nseparates the programs (above) that use the data abstraction from the\nprograms (below) that implement the data abstraction.\n\nPrograms that\nuse rational numbers manipulate them solely in terms of the\nfunctions\nsupplied for public use by the rational-number package:\nadd_rat,\nsub_rat,\nmul_rat,\ndiv_rat,\nand\nequal_rat.\n\nThese, in turn, are implemented solely in terms of the\nmake_rat,\npair,\nhead,\nand\ntail.\n\nIn effect,\nfunctions\nat each level are the interfaces that define the abstraction barriers and\nconnect the different levels.\n\nThis simple idea has many advantages.\n\nOne advantage is that it makes\nprograms much easier to maintain and to modify.\n\nAny complex data\nstructure can be represented in a variety of ways with the primitive\ndata structures provided by a programming language.\n\nOf course, the\nchoice of representation influences the programs that operate on it;\nthus, if the representation were to be changed at some later time, all\nsuch programs might have to be modified accordingly.\n\nThis task could\nbe time-consuming and expensive in the case of large programs unless\nthe dependence on the representation were to be confined by design to\na very few program modules.\n\nFor example, an alternate way to address the problem of functions:\n\n```javascript\nmake_rat_4\n [ 1, 2 ]\n gcd_definition\n print_rat_example5\n\nfunction make_rat(n, d) {\n return pair(n, d);\n}\nfunction numer(x) {\n const g = gcd(head(x), tail(x));\n return head(x) / g;\n}\nfunction denom(x) {\n const g = gcd(head(x), tail(x));\n return tail(x) / g;\n}\n```", + "token_count": 274, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Introduction to Data Abstraction", + "subsection": "Abstraction Barriers", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Data_Abstraction_Barriers_1" + }, + { + "content": "For example, an alternate way to address the problem of functions:\n\n```javascript\nprint_rat_example5\n\n// printing the rational in one line requires some string\n// manipulation: stringify turns a number into a string\n// and the operator + can be applied to strings for\n// string concatenation\nfunction print_rat(x) {\n return display(stringify(numer(x)) + \"/\" + stringify(denom(x)));\n}\n\nconst one_half = make_rat(1, 2);\n\nprint_rat(one_half);\n\nconst one_half = make_rat(1, 2);\n\none_half;\n```\n\nThe difference between this implementation and the previous one lies in when we compute the functions add_rat, sub_rat, and so on do not have to\n\nbe modified at all.\n\nConstraining the dependence on the representation to a few interface\nfunctions\nhelps us design programs as well as modify them, because it allows us to\nmaintain the flexibility to consider alternate implementations.\n\nTo continue\nwith our simple example, suppose we are designing a rational-number package\nand we can t decide initially whether to perform the", + "token_count": 153, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Introduction to Data Abstraction", + "subsection": "Abstraction Barriers", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Data_Abstraction_Barriers_2" + }, + { + "content": "Suppose we want to do\n\nLet us begin by assuming that we already have a way of constructing a\nrational number from a numerator and a denominator.\n\nWe also assume\nthat, given a rational number, we have a way of extracting (or\nselecting) its numerator and its denominator.\n\nLet us further assume\nthat the constructor and selectors are available as\nfunctions:\n-\n-\nmake_rat($n$, $d$)\n$n$ and whose denominator is the integer\n$d$.\n-\n-\nnumer($x$)\n$x$.\n-\n-\ndenom($x$)\n$x$.\n\nWe are using here a powerful strategy of synthesis:\nwishful thinking.\n\nWe haven t yet said how a rational number\nis represented, or how the\nfunctions\nmake_rat\nshould be implemented.\n\nEven so, if we did have these three\nfunctions,\nwe could then add, subtract, multiply, divide, and test equality by using\nthe following relations:\n\\[\n\\begin{array}{rll}\n\\dfrac{n_{1}}{d_{1}}+\\dfrac{n_{2}}{d_{2}}\n&=&\\dfrac{n_{1}d_{2}+n_{2}d_{1}}{d_{1}d_{2}}\\\\[15pt]\n\\dfrac{n_{1}}{d_{1}}-\\dfrac{n_{2}}{d_{2}}\n&=&\\dfrac{n_{1}d_{2}-n_{2}d_{1}}{d_{1}d_{2}}\\\\[15pt]\n\\dfrac{n_{1}}{d_{1}}\\cdot\\dfrac{n_{2}}{d_{2}}\n&=&\\dfrac{n_{1}n_{2}}{d_{1}d_{2}}\\\\[15pt]\n\\dfrac{n_{1}/d_{1}}{n_{2}/d_{2}}\n&=&\\dfrac{n_{1}d_{2}}{d_{1}n_{2}}\\\\[15pt]\n\\dfrac{n_{1}}{d_{1}}\n&=&\\dfrac{n_{2}}{d_{2}}\\ \\quad \\textrm{if and only if}\\ \\ \\ n_{1}d_{2}\\ =\\ n_{2}d_{1}\n\\end{array}\n\\]\n\nWe can express these rules as functions:\n\n```javascript\nadd_rat\n make_rat2\n\nfunction add_rat(x, y) {\n return make_rat(numer(x) * denom(y) + numer(y) * denom(x),\n denom(x) * denom(y));\n}\nfunction sub_rat(x, y) {\n return make_rat(numer(x) * denom(y) - numer(y) * denom(x),\n denom(x) * denom(y));\n}\nfunction mul_rat(x, y) {\n return make_rat(numer(x) * numer(y),\n denom(x) * denom(y));\n}\nfunction div_rat(x, y) {\n return make_rat(numer(x) * denom(y),\n denom(x) * numer(y));\n}\nfunction equal_rat(x, y) {\n return numer(x) * denom(y) === numer(y) * denom(x);\n}\n```\n\nNow we have the operations on rational numbers defined in terms of the\nselector and constructor\nfunctions\nmake_rat.\n\nBut we haven t yet defined these.\n\nWhat we need is some way to glue\ntogether a numerator and a denominator to form a rational number.", "token_count": 283, - "source_files": [ - "chapter2.xml" - ] + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Introduction to Data Abstraction", + "subsection": "Example: Arithmetic Operations for Rational Numbers", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Data_Example_Arithmetic_Operations_for_Rational_Numbers_1" }, { - "chapter_file": "chapter2_chunks.json", - "section": null, - "subsection": "Building Abstractions with Data", - "chunk_id": "chapter2_chunks_Building_Abstractions_with_Data_6", + "content": "What we need is some way to glue\ntogether a numerator and a denominator to form a rational number.\n\nTo enable us to implement the concrete level of our data abstraction, our\nJavaScript environment\nprovides a compound structure called a\npair , which can be constructed with the\nprimitive function\npair.\n\nThis\nfunction\ntakes two arguments and returns a compound data object that contains the\ntwo arguments as parts.\n\nGiven a pair, we can extract the parts using the\nprimitive\nfunctions\nhead\nand\ntail.\n\nThus, we can use\npair,\nhead,\nand\ntail\nas follows:\n\n```javascript\ncons_1_2\n cons_1_2_example\n\nconst x = pair(1, 2);\n```\n\n```javascript\ncons_1_2_example\n\t1\n cons_1_2\n\nhead(x);\n```\n\n```javascript\ncons_1_2_example2\n\t2\n cons_1_2\n\ntail(x);\n```\n\nNotice that a pair is a data object that can be given a name and\nmanipulated, just like a primitive data object.\n\nMoreover,\npair\ncan be used to form pairs whose elements are pairs, and so on:\n\n```javascript\ncons_1_2_3_4\n cons_1_2_3_4_example\n\nconst x = pair(1, 2);\n\nconst y = pair(3, 4);\n\nconst z = pair(x, y);\n```\n\n```javascript\ncons_1_2_3_4_example\n\t1\n cons_1_2_3_4\n\nhead(head(z));\n```\n\n```javascript\ncons_1_2_3_4_example2\n\t3\n cons_1_2_3_4\n\nhead(tail(z));\n```\n\nIn section we will see how this\nability to combine pairs means that pairs can be used as general-purpose\nbuilding blocks to create all sorts of complex data structures.\n\nThe single\ncompound-data primitive pair , implemented by the\nfunctions\npair,\nhead,\nand\ntail,\nis the only glue we need.\n\nData objects constructed from pairs are called\nlist-structured data.\n\nPairs offer a natural way to complete the make_rat,\n\n```javascript\nmake_rat2\n 2\n rat_example_1\n\nfunction make_rat(n, d) { return pair(n, d); }\n\nfunction numer(x) { return head(x); }\n\nfunction denom(x) { return tail(x); }\n```\n\n```javascript\nrat_example_1\n\nnumer(make_rat(2, 3));\n```\n\nAlso, in order to display the results of our computations, we can", + "token_count": 292, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Introduction to Data Abstraction", + "subsection": "Example: Arithmetic Operations for Rational Numbers", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Data_Example_Arithmetic_Operations_for_Rational_Numbers_2" + }, + { + "content": "Also, in order to display the results of our computations, we can\n\n```javascript\ndenominator.\n\tWe use the primitive function\n\tstringify to turn any value (here\n\ta number) into a string. The operator\n\t+ in JavaScript is\n\toverloaded; it can be applied to two numbers or to two strings,\n\tand in the latter case it returns the result of concatenating\n\tthe two strings.\n```\n\n```javascript\nprint_rat\n make_rat2\n print_rat_example_0\n\nfunction print_rat(x) {\n return display(stringify(numer(x)) + \" / \" + stringify(denom(x)));\n}\n```\n\n```javascript\nprint_rat_example_0\n\nconst one_half = make_rat(1, 2);\n\nprint_rat(one_half);\n```\n\nNow we can try our rational-number functions:\n\n```javascript\nprint_rat_example\n [ 1, 2 ]\n make_rat2\n print_rat\n\nconst one_half = make_rat(1, 2);\n\nprint_rat(one_half);\n\nconst one_half = make_rat(1, 2);\n\none_half;\n```\n\n```javascript\none_half\n\nconst one_half = make_rat(1, 2);\n```\n\n```javascript\none_third\n\nconst one_third = make_rat(1, 3);\n```\n\n```javascript\nprint_rat_example2\n [ 5, 6 ]\n add_rat\n one_half\n one_third\n print_rat\n\nprint_rat(add_rat(one_half, one_third));\n\nadd_rat(one_half, one_third);\n```\n\n```javascript\nprint_rat_example3\n [ 1, 6 ]\n add_rat\n one_half\n one_third\n print_rat\n\nprint_rat(mul_rat(one_half, one_third));\n\nmul_rat(one_half, one_third);\n```\n\n```javascript\nprint_rat_example4\n [ 6, 9 ]\n add_rat\n one_third\n print_rat\n\nprint_rat(add_rat(one_third, one_third));\n\nadd_rat(one_third, one_third);\n```\n\nAs the final example shows, our rational-number implementation does not\nmake_rat.\n\nIf we have a\nfunction\nlike the one in section that produces\n\n```javascript\nnumer\n\nfunction numer(x) {\n return head(x);\n}\nfunction denom(x) {\n return tail(x);\n}\n```\n\n```javascript\nmake_rat_3\n [ 2, 3 ]\n numer\n make_rat_3_example_1\n gcd_definition\n\nfunction make_rat(n, d) {\n const g = gcd(n, d);\n return pair(n / g, d / g);\n}\n```\n\n```javascript\nmake_rat_3_example_1\n\nmake_rat(4, 6);\n```\n\nNow we have\n\n```javascript\nnumer_rat\n add_rat_2\n one_third\n print_rat\n\nprint_rat(add_rat(one_third, one_third));\n```", + "token_count": 258, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Introduction to Data Abstraction", + "subsection": "Example: Arithmetic Operations for Rational Numbers", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Data_Example_Arithmetic_Operations_for_Rational_Numbers_3" + }, + { + "content": "Now we have\n\n```javascript\nadd_rat_2\n make_rat_3\n\nfunction add_rat(x, y) {\n return make_rat(numer(x) * denom(y) + numer(y) * denom(x),\n denom(x) * denom(y));\n}\nfunction sub_rat(x, y) {\n return make_rat(numer(x) * denom(y) - numer(y) * denom(x),\n denom(x) * denom(y));\n}\nfunction mul_rat(x, y) {\n return make_rat(numer(x) * numer(y),\n denom(x) * denom(y));\n}\nfunction div_rat(x, y) {\n return make_rat(numer(x) * denom(y),\n denom(x) * numer(y));\n}\nfunction equal_rat(x, y) {\n return numer(x) * denom(y) === numer(y) * denom(x);\n}\n```\n\nas desired.\n\nThis modification was accomplished by changing the constructor\nmake_@rat\nwithout changing any of the\nfunctions\n(such as\nadd_rat\nand\nmul_rat)\nthat implement the actual operations.", + "token_count": 103, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Introduction to Data Abstraction", + "subsection": "Example: Arithmetic Operations for Rational Numbers", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Data_Example_Arithmetic_Operations_for_Rational_Numbers_4" + }, + { + "content": "Alyssa P.\n\nHacker is designing a system to help people solve\nengineering problems.\n\nOne feature she wants to provide in her system\nis the ability to manipulate inexact quantities (such as measured\nparameters of physical devices) with known precision, so that when\ncomputations are done with such approximate quantities the results\nwill be numbers of known precision.\n\nElectrical engineers will be using Alyssa s system to compute\nelectrical quantities.\n\nIt is sometimes necessary for them to compute\nthe value of a parallel equivalent resistance\n$R_{p}$ of two resistors\n$R_{1}$ and $R_{2}$\n\\[\n\\begin{array}{lll}\nR_{p} & = & \\dfrac{1}{1/R_{1}+1/R_{2}}\n\\end{array}\n\\]\nResistance values are usually known only up to some\n6.8 ohms with 10% tolerance you can\nonly be sure that the resistor has a resistance between\n$6.8-0.68=6.12$ and\n$6.8+0.68=7.48$ ohms.\n\nThus, if you have a\n6.8-ohm 10% resistor in parallel with a 4.7-ohm\n5% resistor, the resistance of the combination can range from about\n2.58 ohms (if the two resistors are at the lower bounds) to about 2.97 ohms\n(if the two resistors are at the upper bounds).\n\nAlyssa s idea is to implement interval arithmetic as a\nset of arithmetic operations for combining intervals (objects\nthat represent the range of possible values of an inexact quantity).\n\nThe\nresult of adding, subtracting, multiplying, or dividing two intervals is\nitself an interval, representing the range of the result.\n\nAlyssa postulates the existence of an abstract object called an\ninterval that has two endpoints: a lower bound and an upper bound.\n\nShe also presumes that, given the endpoints of an interval, she can\nconstruct the interval using the data constructor\nmake_interval.\n\nAlyssa first writes a\nfunction\nfor adding two intervals.", + "token_count": 278, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Introduction to Data Abstraction", + "subsection": "Extended Exercise: Interval Arithmetic", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Data_Extended_Exercise_Interval_Arithmetic_1" + }, + { + "content": "Alyssa first writes a\nfunction\nfor adding two intervals.\n\nShe reasons that the minimum value the sum could\nbe is the sum of the two lower bounds and the maximum value it could be is\nthe sum of the two upper bounds:\n\n```javascript\nadd_interval\n make_interval\n print_interval\n [ 4, 7 ]\n add_interval_example\n\nfunction add_interval(x, y) {\n return make_interval(lower_bound(x) + lower_bound(y),\n upper_bound(x) + upper_bound(y));\n}\n```\n\nAlyssa also works out the product of two intervals by finding the\nminimum and the maximum of the products of the bounds and using them\nas the bounds of the resulting interval.\n\n(The functions math_min\nand\nmath_max\nare\nprimitives that find the minimum or maximum of any number of arguments.)\n\n```javascript\nmul_interval\n make_interval\n print_interval\n '[ 3 , 10 ]'\n mul_interval_example\n\nfunction mul_interval(x, y) {\n const p1 = lower_bound(x) * lower_bound(y);\n const p2 = lower_bound(x) * upper_bound(y);\n const p3 = upper_bound(x) * lower_bound(y);\n const p4 = upper_bound(x) * upper_bound(y);\n return make_interval(math_min(p1, p2, p3, p4),\n math_max(p1, p2, p3, p4));\n}\n```\n\nTo divide two intervals, Alyssa multiplies the first by the reciprocal of\nthe second.\n\nNote that the bounds of the reciprocal interval are\nthe reciprocal of the upper bound and the reciprocal of the lower bound, in\nthat order.\n\n```javascript\ndiv_interval\n mul_interval\n print_interval\n '[ 0.2 , 0.6666666666666666 ]'\n div_interval_example\n\nfunction div_interval(x, y) {\n return mul_interval(x, make_interval(1 / upper_bound(y),\n 1 / lower_bound(y)));\n}\n```\n\nAfter debugging her program, Alyssa shows it to a potential user, who\ncomplains that her program solves the wrong problem.\n\nHe wants a program\nthat can deal with numbers represented as a center value and an additive\ntolerance; for example, he wants to work with intervals such as\n$3.5\\pm 0.15$ rather than\n$[3.35, 3.65]$.\n\nAlyssa returns to her desk and\nfixes this problem by supplying an alternate constructor and alternate\nselectors:", + "token_count": 297, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Introduction to Data Abstraction", + "subsection": "Extended Exercise: Interval Arithmetic", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Data_Extended_Exercise_Interval_Arithmetic_2" + }, + { + "content": "Alyssa returns to her desk and\nfixes this problem by supplying an alternate constructor and alternate\nselectors:\n\n```javascript\nmake_center_width\n make_interval\n make_center_width_example\n 0.5\n\nfunction make_center_width(c, w) {\n return make_interval(c - w, c + w);\n}\nfunction center(i) {\n return (lower_bound(i) + upper_bound(i)) / 2;\n}\nfunction width(i) {\n return (upper_bound(i) - lower_bound(i)) / 2;\n}\n```\n\n```javascript\nmake_center_width_example\n make_interval\n\nconst my_interval = make_center_width(1, 0.5);\nwidth(my_interval);\n```\n\nUnfortunately, most of Alyssa s users are engineers.\n\nReal engineering\nsituations usually involve measurements with only a small uncertainty,\nmeasured as the ratio of the width of the interval to the midpoint of the\ninterval.\n\nEngineers usually specify percentage tolerances on the parameters\nof devices, as in the resistor specifications given earlier.\n\nAfter considerable work, Alyssa P.\n\nHacker delivers her finished\nsystem.\n\nSeveral years later, after she has forgotten all about it, she\ngets a frenzied call from an irate user, Lem E.\n\nTweakit.\n\nIt seems that Lem has\nnoticed that the\n\\[\n\\dfrac{R_{1}R_{2}}{R_{1}+R_{2}}\n\\]\nand\n\\[\n\\dfrac{1}{1/R_{1}+1/R_{2}}\n\\]\nHe has written the following two programs, each of which computes the\nparallel-resistors formula differently:\n\n```javascript\npar\n add_interval\n mul_interval\n div_interval\n print_interval\n par_example\n '[ 2 , 4.363636363636363 ][ 2.5454545454545454 , 3.428571428571429 ]'\n\nfunction par1(r1, r2) {\n return div_interval(mul_interval(r1, r2),\n add_interval(r1, r2));\n}\nfunction par2(r1, r2) {\n const one = make_interval(1, 1);\n return div_interval(one,\n add_interval(div_interval(one, r1),\n div_interval(one, r2)));\n}\n```\n\n```javascript\npar_example\n\ndisplay(print_interval(par1(pair(4, 6), pair(7, 8))));\n\ndisplay(print_interval(par2(pair(4, 6), pair(7, 8))));\n\nprint_interval(par1(pair(4, 6), pair(7, 8)))\n+\nprint_interval(par2(pair(4, 6), pair(7, 8)));\n```\n\nLem complains that Alyssa s program gives different answers for\nthe two ways of computing.\n\nThis is a serious complaint.", + "token_count": 262, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Introduction to Data Abstraction", + "subsection": "Extended Exercise: Interval Arithmetic", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Data_Extended_Exercise_Interval_Arithmetic_3" + }, + { + "content": "We began the rational-number implementation in section by implementing the rational-number operations add_rat, sub_rat, and so on in terms of three unspecified functions: make_rat, numerators,\n\ndenominators, and rational numbers whose behavior was specified by the latter three functions.\n\nBut exactly what is meant by data ?\n\nIt is not enough to say\nwhatever is implemented by the given selectors and\nconstructors.\n\nClearly, not every arbitrary set of three\nfunctions\ncan serve as an appropriate basis for the rational-number\nimplementation.\n\nWe need to guarantee that,\nmake_rat,\nmake_rat(n, d),\nthen\n\n```javascript\n\\[\n\t \\begin{array}{lll}\n \\dfrac{\\texttt{numer}(\\texttt{x})}{\\texttt{denom}(\\texttt{x})}\n &=&\n \\dfrac{\\texttt{n}}{\\texttt{d}}\n\t \\end{array}\n \\]\n```\n\nIn fact, this is the only condition make_rat, functions must fulfill in order to be a valid representation.\n\nThis point of view can serve to define not only\nhigh-level data objects, such as rational numbers, but\nlower-level objects as well.\n\nConsider the notion of a\npair, which we used in order to define our\nrational numbers.\n\nWe never actually said what a pair was, only that\nthe language supplied\nfunctions\npair,\nhead,\nand\ntail\nfor operating on pairs.\n\nBut the only thing we need to know about these\nthree operations\nis that if we glue two objects together using\npair\nwe can retrieve the objects using\nhead\nand\ntail.\npair(x, y)\nthen\nhead(z)\nis\ntail(z)\nis\nfunctions\nare included as primitives in our language.\n\nHowever, any triple of\nfunctions\nthat satisfies the above condition can be used as the basis for\nimplementing pairs.\n\nThis point is illustrated strikingly by the fact\nthat we could implement\npair,\nhead,\nand\ntail\nwithout using any data structures at all but only using\nfunctions.\n\nHere are the definitions:", + "token_count": 271, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Introduction to Data Abstraction", + "subsection": "What Is Meant by Data?", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Data_What_Is_Meant_by_Data_1" + }, + { + "content": "Here are the definitions:\n\n```javascript\ncons_with_dispatch\n cons_1_2_run\n 1\n\nfunction pair(x, y) {\n function dispatch(m) {\n return m === 0\n ? x\n : m === 1\n ? y\n : error(m, \"argument not 0 or 1 -- pair\");\n }\n return dispatch;\n}\nfunction head(z) { return z(0); }\n\nfunction tail(z) { return z(1); }\n```\n\n```javascript\ncons_1_2_run\n\nconst x = pair(1, 2);\nhead(x);\n```\n\nThis use of\nfunctions\ncorresponds to nothing like our intuitive notion of what data should be.\n\nNevertheless, all we need to do to show that this is a valid way to\nrepresent pairs is to verify that these\nfunctions\nsatisfy the condition given above.\n\nThe subtle point to notice is that the value returned by pair(x, y) is a functionnamely the internally defined function head(z) is defined to apply\n\nfunction formed by pair(x, y), then head(pair(x, y)) yields tail(pair(x, y)) applies the function returned by pair(x, y) to 1, which returns functional implementation of\n\npairs is a valid implementation, and if we access pairs using only pair, head, and tail we cannot distinguish this implementation from one that uses\n\nreal data structures.\n\nThe point of exhibiting the functional representation of pairs is not that our language works this way\n\n```javascript\n(an efficient implementation of pairs\n might use JavaScript's native\n vector data structure)\n```\n\nbut that it could work this way.\n\nThe\nfunctional\nrepresentation, although obscure, is a perfectly adequate way to represent\npairs, since it fulfills the only conditions that pairs need to fulfill.\n\nThis example also demonstrates that the ability to manipulate\nfunctions\nas objects automatically provides the ability to represent compound data.\n\nThis may seem a curiosity now, but\nfunctional\nrepresentations of data will play a central role in our programming\nrepertoire.", + "token_count": 286, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Introduction to Data Abstraction", + "subsection": "What Is Meant by Data?", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Data_What_Is_Meant_by_Data_2" + }, + { + "content": "This may seem a curiosity now, but\nfunctional\nrepresentations of data will play a central role in our programming\nrepertoire.\n\nThis style of programming is often called\nmessage passing , and we will be using it as a basic tool in\nchapter when we address the issues of modeling and simulation.\n\nDefine\nadd_1).\n\n(Hint: Use substitution to evaluate\nadd_1(zero)).\n\nGive a direct definition of the addition\nfunction plus\n(not in terms of repeated application of\nadd_1).", + "token_count": 76, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Introduction to Data Abstraction", + "subsection": "What Is Meant by Data?", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Data_What_Is_Meant_by_Data_3" + }, + { + "content": "We have introduced data abstraction, a methodology for structuring systems\nin such a way that much of a program can be specified independent of the\nchoices involved in implementing the data objects that the program\nmanipulates.\n\nFor example, we saw in\nsection how to separate the task of\ndesigning a program that uses rational numbers from the task of implementing\nrational numbers in terms of the computer language s primitive\nmechanisms for constructing compound data.\n\nThe key idea was to erect an\nin this case, the selectors and constructors for\nrational numbers\n(make_rat,\nthat isolates the way rational\nnumbers are used from their underlying representation in terms of list\nstructure.\n\nA similar abstraction barrier isolates the details of the\nfunctions\nthat perform rational arithmetic\n(add_rat,\nsub_rat,\nmul_rat,\nand\ndiv_rat)\nfrom the higher-level\nfunctions\nthat use rational numbers.\n\nThe resulting program has the structure shown\nin figure.\n\nThese data-abstraction barriers are powerful tools for controlling\ncomplexity.\n\nBy isolating the underlying representations of data\nobjects, we can divide the task of designing a large program into\nsmaller tasks that can be performed separately.\n\nBut this kind of data\nabstraction is not yet powerful enough, because it may not always make\nsense to speak of the underlying representation for a\ndata object.\n\nFor one thing, there might be more than one useful representation for\na data object, and we might like to design systems that can deal with\nmultiple representations.\n\nTo take a simple example, complex numbers\nmay be represented in two almost equivalent ways: in rectangular form\n(real and imaginary parts) and in polar form (magnitude and angle).\n\nSometimes rectangular form is more appropriate and sometimes polar\nform is more appropriate.", + "token_count": 279, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Data_Multiple_Representations_for_Abstract_Data_1" + }, + { + "content": "Sometimes rectangular form is more appropriate and sometimes polar\nform is more appropriate.\n\nIndeed, it is perfectly plausible to\nimagine a system in which complex numbers are represented in both\nways, and in which the\nfunctions\nfor manipulating complex numbers work with either representation.\n\nMore importantly, programming systems are often designed by many\npeople working over extended periods of time, subject to requirements\nthat change over time.\n\nIn such an environment, it is simply not\npossible for everyone to agree in advance on choices of data\nrepresentation.\n\nSo in addition to the data-abstraction barriers that\nisolate representation from use, we need abstraction barriers that\nisolate different design choices from each other and permit different\nchoices to coexist in a single program.\n\nFurthermore, since large\nprograms are often created by combining\npreexisting\nmodules that were\ndesigned in isolation, we need conventions that permit programmers to\nincorporate modules into larger systems\nadditively , that is,\nwithout having to redesign or reimplement these modules.\n\nIn this section, we will learn how to cope with data that may be\nrepresented in different ways by different parts of a program.\n\nThis\nrequires constructing\ngeneric functions functions\nthat can operate on data that may be represented in more than one way.\n\nOur\nmain technique for building generic\nfunctions\nwill be to work in terms of data objects that have\ntype tags , that is, data objects that include explicit information\nabout how they are to be processed.\n\nWe will also discuss\ndata-directed programming, a powerful and convenient\nimplementation strategy for additively assembling systems with generic\noperations.\n\nWe begin with the simple complex-number example.\n\nWe will see how\ntype tags and data-directed style enable us to design separate\nrectangular and polar representations for complex numbers while\nmaintaining the notion of an abstract\ncomplex-number\ndata object.", + "token_count": 298, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Data_Multiple_Representations_for_Abstract_Data_2" + }, + { + "content": "We will see how\ntype tags and data-directed style enable us to design separate\nrectangular and polar representations for complex numbers while\nmaintaining the notion of an abstract\ncomplex-number\ndata object.\n\nWe will accomplish this by defining arithmetic\nfunctions\nfor complex numbers\n(add_complex,\nsub_complex,\nmul_complex,\nand\ndiv_complex)\nin terms of generic selectors that access parts of a complex number\nindependent of how the number is represented.\n\nThe resulting complex-number\nsystem, as shown in\nfigure,\ncontains two different kinds of\nhorizontal abstraction barriers\nplay the same role as the ones in\nfigure.\n\nThey isolate\nhigher-level operations from lower-level\nrepresentations.\n\nIn addition, there is a vertical barrier\nthat gives us the ability to separately design and install alternative\nrepresentations.\n\n```javascript\nData-abstraction barriers in the\n\t complex-number system.\n```\n\nIn section we will show how to\nuse type tags and data-directed style to develop a generic arithmetic\npackage.\n\nThis provides\nfunctions\n( numbers and\ncan be easily extended when a new kind of number is needed.\n\nIn\nsection , we ll show how to\nuse generic arithmetic in a system that performs symbolic algebra.", + "token_count": 180, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": null, + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Data_Multiple_Representations_for_Abstract_Data_3" + }, + { + "content": "The general strategy of checking the type of a datum and calling an\nappropriate\nfunction\nis called\ndispatching on type.\n\nThis is a powerful strategy for obtaining\nmodularity in system design.\n\nOn the other hand, implementing the dispatch\nas in section has two significant\nweaknesses.\n\nOne weakness is that the generic interface\nfunctions\n(real_part,\nimag_part,\nfunctions\nto check for the new type and apply the appropriate selector for that\nrepresentation.\n\nAnother weakness of the technique is that even though the individual\nrepresentations can be designed separately, we must guarantee that no two\nfunctions\nin the entire system have the same name.\n\nThis is why Ben and Alyssa had\nto change the names of their original\nfunctions\nfrom section.\n\nThe issue underlying both of these weaknesses is that the technique for\nimplementing generic interfaces is not additive.\n\nThe person\nimplementing the generic selector\nfunctions\nmust modify those\nfunctions\neach time a new representation is installed, and the people\ninterfacing the individual representations must modify their\ncode to avoid name conflicts.\n\nIn each of these cases, the changes\nthat must be made to the code are straightforward, but they must be\nmade nonetheless, and this is a source of inconvenience and error.\n\nThis is not much of a problem for the complex-number system as it\nstands, but suppose there were not two but hundreds of different\nrepresentations for complex numbers.\n\nAnd suppose that there were many\ngeneric selectors to be maintained in the abstract-data interface.\n\nSuppose, in fact, that no one programmer knew all the interface\nfunctions\nor all the representations.\n\nThe problem is real and must\nbe addressed in such programs as\nlarge-scale data-base-management systems.\n\nWhat we need is a means for modularizing the system design even\nfurther.\n\nThis is provided by the programming technique known as\ndata-directed programming.", + "token_count": 298, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": "Data-Directed Programming and Additivity", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Data_Data-Directed_Programming_and_Additivity_1" + }, + { + "content": "This is provided by the programming technique known as\ndata-directed programming.\n\nTo understand how data-directed\nprogramming works, begin with the observation that whenever we deal\nwith a set of generic operations that are common to a set of\ndifferent types we are, in effect, dealing with a two-dimensional\ntable that contains the possible operations on one axis and the\npossible types on the other axis.\n\nThe entries in the table are the\nfunctions\nthat implement each operation for each type of argument presented.\n\nIn the complex-number system developed in the previous section, the\ncorrespondence between operation name, data type, and actual\nfunction\nwas spread out among the various conditional clauses in the generic\ninterface\nfunctions.\n\nBut the same information could have been organized in a table, as shown in\nfigure.\n\nData-directed programming is the technique of designing programs to work\nwith such a\nfunctions\nthat each perform an explicit dispatch on type.\n\nHere we will implement the\ninterface as a single\nfunction\nthat looks up the combination of the operation name and argument type in\nthe table to find the correct\nfunction\nto apply, and then applies it to the contents of the argument.\n\nIf we do\nthis, then to add a new representation package to the system we need not\nchange any existing\n\n```javascript\nfunctions;\n```\n\nwe need only add new entries to the table.\n\nTable of operations for the complex-number system.\n\nTo implement this plan, assume that we have two\nfunctions,\n-\n-\nput(op, type, item)\ninstalls the\nitem\nin the table, indexed by the\n\n```javascript\nop and the\n\t type.\n```\n\n- - get(op, type) looks up the\n\n```javascript\nop,\n\t type\n```\n\nentry in the table and returns the item found there.\n\nIf no item is found,", + "token_count": 289, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": "Data-Directed Programming and Additivity", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Data_Data-Directed_Programming_and_Additivity_2" + }, + { + "content": "If no item is found,\n\n```javascript\na unique primitive value that is referred to by the name\n\t undefined and recognized\n\t by the primitive predicate\n\t is_undefined.\n```\n\nFor now, we can assume that (section ) we will see how to implement these and other operations for manipulating tables.\n\nHere is how data-directed programming can be used in the complex-number\nsystem.\n\nBen, who developed the rectangular representation, implements his\ncode just as he did originally.\n\nHe defines a collection of\nfunctions\nor a\npackage , and interfaces these to the rest of the system by adding\nentries to the table that tell the system how to operate on rectangular\nnumbers.\n\nThis is accomplished by calling the following\nfunction:\n\n```javascript\noperation_table_from_chapter_3\n\n// operation_table, put and get\n// from chapter 3 (section 3.3.3)\n```\n\n```javascript\ninstall_rectangular_package_usage\n\ninstall_rectangular_package();\n```\n\n```javascript\ninstall_rectangular_package\n operation_table_from_chapter_3\n operation_table\n attach_tag\n square_definition\n 'done'\n install_rectangular_package_usage\n\nfunction install_rectangular_package() {\n // internal functions\n function real_part(z) { return head(z); }\n function imag_part(z) { return tail(z); }\n function make_from_real_imag(x, y) { return pair(x, y); }\n function magnitude(z) {\n return math_sqrt(square(real_part(z)) + square(imag_part(z)));\n }\n function angle(z) {\n return math_atan2(imag_part(z), real_part(z));\n }\n function make_from_mag_ang(r, a) {\n return pair(r * math_cos(a), r * math_sin(a));\n }\n\n // interface to the rest of the system\n function tag(x) { return attach_tag(\"rectangular\", x); }\n put(\"real_part\", list(\"rectangular\"), real_part);\n put(\"imag_part\", list(\"rectangular\"), imag_part);\n put(\"magnitude\", list(\"rectangular\"), magnitude);\n put(\"angle\", list(\"rectangular\"), angle);\n put(\"make_from_real_imag\", \"rectangular\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"rectangular\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\n```\n\nNotice that the internal\nfunctions\nhere are the same\nfunctions\nfrom section that\nBen wrote when he was working in isolation.\n\nNo changes are necessary in\norder to interface them to the rest of the system.\n\nMoreover, since these\nfunction declarations\nare internal to the installation\nfunction,\nBen needn t worry about name conflicts with other\nfunctions\noutside the rectangular package.", + "token_count": 301, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": "Data-Directed Programming and Additivity", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Data_Data-Directed_Programming_and_Additivity_3" + }, + { + "content": "Moreover, since these\nfunction declarations\nare internal to the installation\nfunction,\nBen needn t worry about name conflicts with other\nfunctions\noutside the rectangular package.\n\nTo interface these to the rest of the\nsystem, Ben installs his\nreal_part\nfunction\nunder the operation name\nreal_part\nand the type\nlist(\"rectangular\"),\nand similarly for the other selectors. s internally defined\nconstructors, except that they attach the tag.\n\nAlyssa s\n\n```javascript\ninstall_polar_package\n operation_table_from_chapter_3\n operation_table\n attach_tag\n square_definition\n install_polar_package_usage\n 'done'\n\nfunction install_polar_package() {\n // internal functions\n function magnitude(z) { return head(z); }\n function angle(z) { return tail(z); }\n function make_from_mag_ang(r, a) { return pair(r, a); }\n function real_part(z) {\n return magnitude(z) * math_cos(angle(z));\n }\n function imag_part(z) {\n return magnitude(z) * math_sin(angle(z));\n }\n function make_from_real_imag(x, y) {\n return pair(math_sqrt(square(x) + square(y)),\n math_atan2(y, x));\n }\n\n // interface to the rest of the system\n function tag(x) { return attach_tag(\"polar\", x); }\n put(\"real_part\", list(\"polar\"), real_part);\n put(\"imag_part\", list(\"polar\"), imag_part);\n put(\"magnitude\", list(\"polar\"), magnitude);\n put(\"angle\", list(\"polar\"), angle);\n put(\"make_from_real_imag\", \"polar\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"polar\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\n```\n\n```javascript\ninstall_polar_package_usage\n install_polar_package\n\ninstall_polar_package();\n```\n\nEven though Ben and Alyssa both still use their original functions defined with the same names as each other s (e.g., real_part), these declarations are\n\nnow internal to different functions (see section ), so there is no name conflict.\n\nThe complex-arithmetic selectors access the table by means of a general operation function called apply_generic, which applies a generic operation to some arguments.\n\n```javascript\nThe function\n\tapply_generic\n```\n\nlooks in the table under the name of the operation and the types of the arguments and applies the resulting function if one is present:", + "token_count": 269, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": "Data-Directed Programming and Additivity", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Data_Data-Directed_Programming_and_Additivity_4" + }, + { + "content": "looks in the table under the name of the operation and the types of the arguments and applies the resulting function if one is present:\n\n```javascript\napply_definition\n\n// In Source, most functions have a fixed number of arguments.\n// (The function list is the only exception, to this so far.)\n// The function apply_in_underlying_javascript allows us to\n// apply any given function fun to all elements of the argument\n// list args, as if they were separate arguments\nfunction apply(fun, args) {\n return apply_in_underlying_javascript(fun, args);\n}\n```\n\n```javascript\napply_generic\n apply_definition\n\nfunction apply_generic(op, args) {\n const type_tags = map(type_tag, args);\n const fun = get(op, type_tags);\n return ! is_undefined(fun)\n ? apply_in_underlying_javascript(fun, map(contents, args))\n : error(list(op, type_tags),\n \"no method for these types -- apply_generic\");\n}\n```\n\nUsing apply_generic, we can define our generic selectors as follows:\n\n```javascript\ngeneric_selectors\n apply_generic\n generic_selectors_example\n 9\n\nfunction real_part(z) { return apply_generic(\"real_part\", list(z)); }\n\nfunction imag_part(z) { return apply_generic(\"imag_part\", list(z)); }\n\nfunction magnitude(z) { return apply_generic(\"magnitude\", list(z)); }\n\nfunction angle(z) { return apply_generic(\"angle\", list(z)); }\n```\n\n```javascript\ngeneric_selectors_example\n install_rectangular_package\n install_rectangular_package_usage\n install_polar_package\n complex_number_calculation\n generic_constructors\n\nconst my_complex_number =\n make_from_real_imag(1.0, 4.5);\n\nconst result =\n add_complex(my_complex_number,\n my_complex_number);\n\nimag_part(result);\n```\n\nObserve that these do not change at all if a new representation is added to the system.\n\nWe can also extract from the table the constructors to be used by the\nprograms external to the packages in making complex numbers from real and\nimaginary parts and from magnitudes and angles.\n\nAs in\nsection , we construct rectangular\nnumbers whenever we have real and imaginary parts, and polar numbers\nwhenever we have magnitudes and angles:\n\n```javascript\ngeneric_constructors\n generic_selectors\n generic_selectors_example\n 9\n\nfunction make_from_real_imag(x, y) {\n return get(\"make_from_real_imag\", \"rectangular\")(x, y);\n}\nfunction make_from_mag_ang(r, a) {\n return get(\"make_from_mag_ang\", \"polar\")(r, a);\n}\n```\n\nThe key idea of data-directed programming is to handle generic operations\nin programs by dealing explicitly with operation-and-type tables, such as\nthe table in\nfigure.", + "token_count": 309, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": "Data-Directed Programming and Additivity", + "chunk_index": 5, + "chunk_id": "Building_Abstractions_with_Data_Data-Directed_Programming_and_Additivity_5" + }, + { + "content": "The key idea of data-directed programming is to handle generic operations\nin programs by dealing explicitly with operation-and-type tables, such as\nthe table in\nfigure.\n\nThe style of programming we used in\nsection organized the required\ndispatching on type by having each operation take care of its own\ndispatching.\n\nIn effect, this decomposes the operation-and-type table into\nrows, with each generic operation\nfunction\nrepresenting a row of the table.\n\nAn alternative implementation strategy is to decompose the table into\ncolumns and, instead of using intelligent operations that\ndispatch on data types, to work with intelligent data\nobjects that dispatch on operation names.\n\nWe can do this by\narranging things so that a data object, such as a rectangular number, is\nrepresented as a\nfunction\nthat takes as input the required operation name and performs the operation\nindicated.\n\nIn such a discipline,\nmake_from_real_imag\ncould be written as", + "token_count": 146, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": "Data-Directed Programming and Additivity", "chunk_index": 6, - "content": "One key idea in dealing with compound data is the notion of closure that the glue we use for combining data objects should allow us to combine not only primitive data objects, but compound data objects as well. Another key idea is that compound data objects can serve as conventional interfaces for combining program modules in mix-and-match ways. We illustrate some of these ideas by presenting a simple graphics language that exploits closure. We will then augment the representational power of our language by introducing symbolic expressions data whose elementary parts can be arbitrary symbols rather than only numbers. We explore various alternatives for representing sets of objects. We will find that, just as a given numerical function can be computed by many different computational processes, there are many ways in which a given data structure can be represented in terms of simpler objects, and the choice of representation can have significant impact on the time and space requirements of processes that manipulate the data. We will investigate these ideas in the context of symbolic differentiation, the representation of sets, and the encoding of information. Next we will take up the problem of working with data that may be represented differently by different parts of a program. This leads to the need to implement generic operations , which must handle many different types of data. Maintaining modularity in the presence of generic operations requires more powerful abstraction barriers than can be erected with simple data abstraction alone.", - "token_count": 268, - "source_files": [ - "chapter2.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Data-Directed_Programming_and_Additivity_6" }, { - "chapter_file": "chapter2_chunks.json", - "section": null, - "subsection": "Building Abstractions with Data", - "chunk_id": "chapter2_chunks_Building_Abstractions_with_Data_7", + "content": "In such a discipline,\nmake_from_real_imag\ncould be written as\n\n```javascript\nmake_from_real_imag_message_passing\n square_definition\n message_passing_example\n 9\n\nfunction make_from_real_imag(x, y) {\n function dispatch(op) {\n return op === \"real_part\"\n ? x\n : op === \"imag_part\"\n ? y\n : op === \"magnitude\"\n ? math_sqrt(square(x) + square(y))\n : op === \"angle\"\n ? math_atan2(y, x)\n : error(op, \"unknown op -- make_from_real_imag\");\n }\n return dispatch;\n}\n\nfunction make_from_real_imag(x, y) {\n function dispatch(op) {\n return op === \"real_part\"\n ? x\n : op === \"imag_part\"\n ? y\n : op === \"magnitude\"\n ? math_sqrt(square(x) + square(y))\n : op === \"angle\"\n ? math_atan2(y, x)\n : error(op, \"unknown op -- make_from_real_imag\");\n }\n return dispatch;\n}\n\nfunction make_from_mag_ang(r, a) {\n function dispatch(op) {\n return op === \"real_part\"\n ? r * math_cos(a)\n : op === \"imag_part\"\n ? r * math_sin(a)\n : op === \"magnitude\"\n ? r\n : op === \"angle\"\n ? a\n : error(op, \"unknown op -- make_from_real_imag\");\n }\n return dispatch;\n}\n\nfunction apply_generic(op, arg) {\n return head(arg)(op);\n}\nfunction real_part(z) {\n return apply_generic(\"real_part\", list(z));\n}\nfunction imag_part(z) {\n return apply_generic(\"imag_part\", list(z));\n}\nfunction magnitude(z) {\n return apply_generic(\"magnitude\", list(z));\n}\nfunction angle(z) {\n return apply_generic(\"angle\", list(z));\n}\nfunction add_complex(z1, z2) {\n return make_from_real_imag(\n real_part(z1) + real_part(z2),\n imag_part(z1) + imag_part(z2));\n}\nfunction sub_complex(z1, z2) {\n return make_from_real_imag(\n real_part(z1) - real_part(z2),\n imag_part(z1) - imag_part(z2));\n}\nfunction mul_complex(z1, z2) {\n return make_from_mag_ang(\n magnitude(z1) * magnitude(z2),\n angle(z1) + angle(z2));\n}\nfunction div_complex(z1, z2) {\n return make_from_mag_ang(\n magnitude(z1) / magnitude(z2),\n angle(z1) - angle(z2));\n}\n// operation_table, put and get\n// from chapter 3 (section 3.3.3)\nfunction assoc(key, records) {\n return is_null(records)\n ? undefined\n : equal(key, head(head(records)))\n ? head(records)\n : assoc(key, tail(records));\n}\nfunction make_table() {\n const local_table = list(\"*table*\");\n function lookup(key_1, key_2) {\n const subtable = assoc(key_1, tail(local_table));\n if (is_undefined(subtable)) {\n return undefined;\n } else {\n const record = assoc(key_2, tail(subtable));\n return is_undefined(record)\n ? undefined\n : tail(record);\n }\n }\n function insert(key_1, key_2, value) {\n const subtable = assoc(key_1, tail(local_table));\n if (is_undefined(subtable)) {\n set_tail(local_table,\n pair(list(key_1, pair(key_2, value)),\n tail(local_table)));\n } else {\n const record = assoc(key_2, tail(subtable));\n if (is_undefined(record)) {\n \t set_tail(subtable,\n\t pair(pair(key_2, value),\n tail(subtable)));\n\t } else {\n set_tail(record, value);\n }\n }\n }\n function dispatch(m) {\n return m === \"lookup\"\n ? lookup\n : m === \"insert\"\n ? insert\n : \"undefined operation -- table\";\n }\n return dispatch;\n}\nconst operation_table = make_table();\nconst get = operation_table(\"lookup\");\nconst put = operation_table(\"insert\");\nfunction attach_tag(type_tag, contents) {\n return pair(type_tag, contents);\n}\nfunction type_tag(datum) {\n return is_pair(datum)\n ? head(datum)\n : error(datum, \"bad tagged datum -- type_tag\");\n}\nfunction contents(datum) {\n return is_pair(datum)\n ? tail(datum)\n : error(datum, \"bad tagged datum -- contents\");\n}\nfunction install_rectangular_package() {\n function real_part(z) { return head(z); }\n function imag_part(z) { return tail(z); }\n function make_from_real_imag(x, y) { return pair(x, y); }\n function magnitude(z) {\n return math_sqrt(square(real_part(z)) +\n square(imag_part(z)));\n }\n function angle(z) {\n return math_atan2(imag_part(z), real_part(z));\n }\n function make_from_mag_ang(r, a) {\n return pair(r * math_cos(a), r * math_sin(a));\n }\n // interface to the rest of the system\n function tag(x) {\n return attach_tag(\"rectangular\", x);\n }\n put(\"real_part\", list(\"rectangular\"), real_part);\n put(\"imag_part\", list(\"rectangular\"), imag_part);\n put(\"magnitude\", list(\"rectangular\"), magnitude);\n put(\"angle\", list(\"rectangular\"), angle);\n put(\"make_from_real_imag\", \"rectangular\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"rectangular\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\n\ninstall_rectangular_package();\nfunction install_polar_package() {\n // internal functions\n function magnitude(z) { return head(z); }\n function angle(z) { return tail(z); }\n function make_from_mag_ang(r, a) { return pair(r, a); }\n function real_part(z) {\n return magnitude(z) * math_cos(angle(z));\n }\n function imag_part(z) {\n return magnitude(z) * math_sin(angle(z));\n }\n function make_from_real_imag(x, y) {\n return pair(math_sqrt(square(x) + square(y)),\n math_atan2(y, x));\n }\n\n // interface to the rest of the system\n function tag(x) { return attach_tag(\"polar\", x); }\n put(\"real_part\", list(\"polar\"), real_part);\n put(\"imag_part\", list(\"polar\"), imag_part);\n put(\"magnitude\", list(\"polar\"), magnitude);\n put(\"angle\", list(\"polar\"), angle);\n put(\"make_from_real_imag\", \"polar\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"polar\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\n\ninstall_polar_package();\n```", + "token_count": 608, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": "Data-Directed Programming and Additivity", "chunk_index": 7, - "content": "In particular, we introduce data-directed programming as a technique that allows individual data representations to be designed in isolation and then combined additively (i.e., without modification). To illustrate the power of this approach to system design, we close the chapter by applying what we have learned to the implementation of a package for performing symbolic arithmetic on polynomials, in which the coefficients of the polynomials can be integers, rational numbers, complex numbers, and even other polynomials.", - "token_count": 93, - "source_files": [ - "chapter2.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Data-Directed_Programming_and_Additivity_7" + }, + { + "content": "In such a discipline,\nmake_from_real_imag\ncould be written as\n\nThe corresponding apply_generic function, which applies a generic operation to an argument, now simply feeds the operation s name to the data object and lets\n\nthe object do the work:\n\n```javascript\napply_generic_message_passing\n make_from_real_imag_message_passing\n message_passing_example\n\nfunction apply_generic(op, arg) { return head(arg)(op); }\n```\n\n```javascript\nmessage_passing_example\n\nconst my_complex_number =\n make_from_real_imag(1.0, 4.5);\n\nconst result =\n add_complex(my_complex_number,\n my_complex_number);\n\nimag_part(result);\n```\n\n```javascript\ngeneric_selectors_message_passing\n apply_generic_message_passing\n generic_selectors_example\n\nfunction real_part(z) {\n return apply_generic(\"real_part\", list(z));\n}\nfunction imag_part(z) {\n return apply_generic(\"imag_part\", list(z));\n}\nfunction magnitude(z) {\n return apply_generic(\"magnitude\", list(z));\n}\nfunction angle(z) {\n return apply_generic(\"angle\", list(z));\n}\n```\n\nNote that the value returned by\nmake_from_real_imag\nis a\nfunctionthe internal\nfunction.\n\nThis is the\nfunction\nthat is invoked when\napply_generic\nrequests an operation to be performed.\n\nThis style of programming is called message passing.\n\nThe name\ncomes from the image that a data object is an entity that receives the\nrequested operation name as a message.\n\nWe have already seen\nan example of message passing in section ,\nwhere we saw how\npair,\nhead,\nand\ntail\ncould be defined with no data objects but only\nfunctions.\n\nHere we see that message passing is not a mathematical trick but a useful\ntechnique for organizing systems with generic operations.\n\nIn the remainder\nof this chapter we will continue to use data-directed programming, rather\nthan message passing, to discuss generic arithmetic operations.\n\nIn\nchapter we will return to message passing, and we will see that\nit can be a powerful tool for structuring simulation programs.", + "token_count": 251, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": "Data-Directed Programming and Additivity", + "chunk_index": 8, + "chunk_id": "Building_Abstractions_with_Data_Data-Directed_Programming_and_Additivity_8" }, { - "chapter_file": "chapter2_chunks.json", - "section": "Building Abstractions with Data", - "subsection": "Hierarchical Data and the Closure Property", - "chunk_id": "chapter2_chunks_Hierarchical_Data_and_the_Closure_Property_1", + "content": "We will develop a system that performs arithmetic operations on complex\nnumbers as a simple but unrealistic example of a program that uses generic\noperations.\n\nWe begin by discussing two plausible representations for\ncomplex numbers as ordered pairs: rectangular form (real part and imaginary\npart) and polar form (magnitude and angle).\nwill show how both representations can be made to coexist in a single\nsystem through the use of type tags and generic operations.\n\nLike rational numbers, complex numbers are naturally represented as ordered\npairs.\n\nThe set of complex numbers can be thought of as a two-dimensional\nspace with two orthogonal axes, the real axis and the\nimaginary axis.\n\n(See\nfigure.) From this point of view,\nthe complex number $z=x+iy$ (where\n$i^{2} =-1$ ) can be thought of as the point in\nthe plane whose real coordinate is $x$ and whose\nimaginary coordinate is $y$.\n\nAddition of complex\nnumbers reduces in this representation to addition of coordinates:\n\\[\n\\begin{array}{lll}\n\\mbox{Real-part}(z_{1}+z_{2}) & = &\n\\mbox{Real-part}(z_{1})+\\mbox{Real-part}(z_{2}) \\\\[1ex]\n\\mbox{Imaginary-part}(z_{1} +z_{2}) & = &\n\\mbox{Imaginary-part}(z_1)+\\mbox{Imaginary-part}(z_2)\n\\end{array}\n\\]\n\nWhen multiplying complex numbers, it is more natural to think in terms\nof representing a complex number in polar form, as a magnitude and an\nangle ( $r$ and $A$\nin figure ).\n\nThe product of two\ncomplex numbers is the vector obtained by stretching one complex number by\n\\[\n\\begin{array}{lll}\n\\mbox{Magnitude}(z_{1}\\cdot z_{2}) & = &\n\\mbox{Magnitude}(z_{1})\\cdot\\mbox{Magnitude}(z_{2})\\\\[1ex]\n\\mbox{Angle}(z_{1}\\cdot z_{2}) & = &\n\\mbox{Angle}(z_{1})+\\mbox{Angle}(z_{2})\n\\end{array}\n\\]\n\nThus, there are two different representations for complex numbers,\nwhich are appropriate for different operations.\n\nYet, from the\nviewpoint of someone writing a program that uses complex numbers, the\nprinciple of data abstraction suggests that all the operations for\nmanipulating complex numbers should be available regardless of which\nrepresentation is used by the computer.", + "token_count": 292, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": "Representations for Complex Numbers", "chunk_index": 1, - "content": "As we have seen, pairs provide a primitive glue that we can use to construct compound data objects. Figure Figure shows a standard way to visualize a in this case, the pair formed by (cons 1 2) . pair(1, 2) . In this representation, which is called box-and-pointer notation , each object is shown as a pointer to a box. The box for a primitive object contains a representation of the object. For example, the box for a number contains a numeral. The box for a pair is actually a double box, the left part containing (a pointer to) the car of the pair and the right part containing the cdr . In this representation, which is called box-and-pointer notation , each compound object is shown as a pointer to a box. The box for a pair has two parts, the left part containing the head of the pair and the right part containing the tail. We have already seen that cons pair can be used to combine not only numbers but pairs as well. (You made use of this fact, or should have, in doing exercises and .) As a consequence, pairs provide a universal building block from which we can construct all sorts of data structures. Figure Figure shows two ways to use pairs to combine the numbers 1, 2, 3, and 4. Two ways to combine 1, 2, 3, and 4 using pairs.", - "token_count": 287, - "source_files": [ - "section2.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Representations_for_Complex_Numbers_1" }, { - "chapter_file": "chapter2_chunks.json", - "section": "Building Abstractions with Data", - "subsection": "Hierarchical Data and the Closure Property", - "chunk_id": "chapter2_chunks_Hierarchical_Data_and_the_Closure_Property_2", + "content": "Yet, from the\nviewpoint of someone writing a program that uses complex numbers, the\nprinciple of data abstraction suggests that all the operations for\nmanipulating complex numbers should be available regardless of which\nrepresentation is used by the computer.\n\nFor example, it is often\nuseful to be able to find the magnitude of a complex number that is\nspecified by rectangular coordinates.\n\nSimilarly, it is often useful\nto be able to determine the real part of a complex number that is\nspecified by polar coordinates.\n\nComplex numbers as points in the plane.\n\nTo design such a system, we can follow the same.\n\nAssume that the\noperations on complex numbers are implemented in terms of four selectors:\nreal_part,\nimag_part,\nmagnitude,\nand\nfunctions\nfor constructing complex numbers:\nmake_from_real_imag\nreturns a complex number with specified real and imaginary parts, and\nmake_from_mag_ang\nreturns a complex number with specified magnitude and angle.\n\nThese\nfunctions\nhave the property that, for any complex number\n\n```javascript\nmake_from_real_imag(real_part(z), imag_part(z));\n```\n\nand\n\n```javascript\nmake_from_mag_ang(magnitude(z), angle(z));\n```\n\nproduce complex numbers that are equal to\n\nUsing these constructors and selectors, we can implement arithmetic on\ncomplex numbers using the abstract data specified by the\nconstructors and selectors, just as we did for rational numbers in\nsection.\n\nAs shown in the formulas\nabove, we can add and subtract complex numbers in terms of real and\nimaginary parts while multiplying and dividing complex numbers in terms of\nmagnitudes and angles:\n\n```javascript\ncomplex_number_calculation\n\nfunction add_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) + real_part(z2),\n imag_part(z1) + imag_part(z2));\n}\nfunction sub_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) - real_part(z2),\n imag_part(z1) - imag_part(z2));\n}\nfunction mul_complex(z1, z2) {\n return make_from_mag_ang(magnitude(z1) * magnitude(z2),\n angle(z1) + angle(z2));\n}\nfunction div_complex(z1, z2) {\n return make_from_mag_ang(magnitude(z1) / magnitude(z2),\n angle(z1) - angle(z2));\n}\n```", + "token_count": 287, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": "Representations for Complex Numbers", "chunk_index": 2, - "content": "Two ways to combine 1, 2, 3, and 4 using pairs. The ability to create pairs whose elements are pairs is the essence of list structure s importance as a representational tool. We refer to this ability as the closure property of cons . pair . In general, an operation for combining data objects satisfies the closure property if the results of combining things with that operation can themselves be combined using the same operation. hierarchical structures structures made up of parts, which themselves are made up of parts, and so on. From the outset of chapter , we ve made essential use of closure in dealing with procedures, functions, because all but the very simplest programs rely on the fact that the elements of a combination can themselves be combinations. In this section, we take up the consequences of closure for compound data. We describe some conventional techniques for using pairs to represent sequences and trees, and we exhibit a graphics language that illustrates closure in a vivid way.", - "token_count": 192, - "source_files": [ - "section2.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Representations_for_Complex_Numbers_2" }, { - "chapter_file": "chapter2_chunks.json", - "section": "Hierarchical Data and the Closure Property", - "subsection": "Hierarchical Structures", - "chunk_id": "chapter2_chunks_Hierarchical_Structures_1", + "content": "As shown in the formulas\nabove, we can add and subtract complex numbers in terms of real and\nimaginary parts while multiplying and dividing complex numbers in terms of\nmagnitudes and angles:\n\nTo complete the complex-number package, we must choose a representation and\nwe must implement the constructors and selectors in terms of primitive\nnumbers and primitive list structure.\n\nThere are two obvious ways to do\nthis: We can represent a complex number in rectangular form\nas a pair (real part, imaginary part) or in polar form as a\npair (magnitude, angle).\n\nWhich shall we choose?\n\nIn order to make the different choices concrete, imagine that there are two\nprogrammers, Ben Bitdiddle and Alyssa P.\n\nHacker, who are independently\ndesigning representations for the complex-number system.\n\nBen chooses to represent\n\\[\n\\begin{array}{lllllll}\nx & = & r\\ \\cos A & \\quad \\quad \\quad & r & = & \\sqrt{x^2 +y^2} \\\\\ny & = & r\\ \\sin A & & A &= & \\arctan (y,x)\n\\end{array}\n\\]\nwhich relate the real and imaginary parts ( $x$ ,\n$y$ ) to the magnitude and the angle\n$(r, A)$. s representation is therefore given by the following selectors\nand constructors:\n\n```javascript\nmake_complex_number1\n complex_number_calculation\n square_definition\n make_complex_number_example\n -3\n\nfunction real_part(z) { return head(z); }\n\nfunction imag_part(z) { return tail(z); }\n\nfunction magnitude(z) {\n return math_sqrt(square(real_part(z)) + square(imag_part(z)));\n}\nfunction angle(z) {\n return math_atan2(imag_part(z), real_part(z));\n}\nfunction make_from_real_imag(x, y) { return pair(x, y); }\n\nfunction make_from_mag_ang(r, a) {\n return pair(r * math_cos(a), r * math_sin(a));\n}\n```\n\n```javascript\nmake_complex_number_example\n\nconst my_co_num_1 = make_from_real_imag(2.5, -0.5);\nconst my_co_num_2 = make_from_real_imag(2.5, -0.5);\n\nconst result = add_complex(my_co_num_1,\n mul_complex(my_co_num_2,\n my_co_num_2));\n\nimag_part(result);\n```\n\nAlyssa, in contrast, chooses to represent complex numbers in For her, selecting the magnitude and angle is straightforward, but she has to use the s\n\nrepresentation is:", + "token_count": 298, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": "Representations for Complex Numbers", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Data_Representations_for_Complex_Numbers_3" + }, + { + "content": "representation is:\n\n```javascript\nmake_complex_number2\n complex_number_calculation\n square_definition\n make_complex_number_example\n -3\n\nfunction real_part(z) {\n return magnitude(z) * math_cos(angle(z));\n}\nfunction imag_part(z) {\n return magnitude(z) * math_sin(angle(z));\n}\nfunction magnitude(z) { return head(z); }\n\nfunction angle(z) { return tail(z); }\n\nfunction make_from_real_imag(x, y) {\n return pair(math_sqrt(square(x) + square(y)),\n math_atan2(y, x));\n}\nfunction make_from_mag_ang(r, a) { return pair(r, a); }\n```\n\nThe discipline of data abstraction ensures that the same implementation of add_@complex, sub_complex, mul_complex, and div_complex will work with either Ben s representation or Alyssa\n\ns representation.", + "token_count": 83, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": "Representations for Complex Numbers", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Data_Representations_for_Complex_Numbers_4" + }, + { + "content": "One way to view data abstraction is as an application of the\nprinciple of least commitment.\n\nIn implementing the\ncomplex-number system in\nsection , we can\nuse either Ben s rectangular representation or Alyssa s polar\nrepresentation.\n\nThe abstraction barrier formed by the selectors and\nconstructors permits us to defer to the last possible moment the choice of\na concrete representation for our data objects and thus retain maximum\nflexibility in our system design.\n\nThe principle of least commitment can be carried to even further extremes.\n\nIf we desire, we can maintain the ambiguity of representation even\nafter we have designed the selectors and constructors, and elect\nto use both Ben s representation and Alyssa s\nrepresentation.\n\nIf both representations are included in a single system,\nhowever, we will need some way to distinguish data in polar form from data\nin rectangular form.\n\nOtherwise, if we were asked, for instance, to find\nthe $(3,4)$ , we wouldn t know whether to\nanswer 5 (interpreting the number in rectangular form) or\n3 (interpreting the number in polar form).\n\nA straightforward way to\naccomplish this distinction is to include a\ntype tag the\nstring \"rectangular\"\nor\n\"polar\"as\npart of each complex number.\n\nThen when we need to manipulate a complex\nnumber we can use the tag to decide which selector to apply.\n\nIn order to manipulate tagged data, we will assume that we have\nfunctions\ntype_tag\nand\nfunction\nattach_tag\nthat takes a tag and contents and produces a tagged data object.\n\nA\nstraightforward way to implement this is to use ordinary list structure:", + "token_count": 261, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": "Tagged data", "chunk_index": 1, - "content": "The representation of sequences in terms of lists generalizes naturally to represent sequences whose elements may themselves be sequences. For example, we can regard the object ((1 2) 3 4) [[1, [2, null]], [3, [4, null]]] constructed by (cons (list 1 2) (list 3 4)) pair(list(1, 2), list(3, 4)); as a list of three items, the first of which is itself a list, (1 2) . Indeed, this is suggested by the form in which the result is printed by the interpreter. [1, [2, null]] . Figure Figure shows the representation of this structure in terms of pairs. Structure formed by (cons (list 1 2) (list 3 4)) . Structure formed by pair(list(1, 2), list(3, 4)) .\nAnother way to think of sequences whose elements are sequences is as trees . The elements of the sequence are the branches of the tree, and elements that are themselves sequences are subtrees. Figure Figure shows the structure in figure figure viewed as a tree. The list structure in figure viewed as a tree. The list structure in figure viewed as a tree.", - "token_count": 259, - "source_files": [ - "subsection2.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Tagged_data_1" }, { - "chapter_file": "chapter2_chunks.json", - "section": "Hierarchical Data and the Closure Property", - "subsection": "Hierarchical Structures", - "chunk_id": "chapter2_chunks_Hierarchical_Structures_2", + "content": "A\nstraightforward way to implement this is to use ordinary list structure:\n\n```javascript\nattach_tag\n attach_tag_example\n 'frequency_list'\n\nfunction attach_tag(type_tag, contents) {\n return pair(type_tag, contents);\n}\nfunction type_tag(datum) {\n return is_pair(datum)\n ? head(datum)\n : error(datum, \"bad tagged datum -- type_tag\");\n}\nfunction contents(datum) {\n return is_pair(datum)\n ? tail(datum)\n : error(datum, \"bad tagged datum -- contents\");\n}\n```\n\n```javascript\nattach_tag_example\n\nconst f_1 = list(\"A\", 4);\nconst my_frequency_1 =\n attach_tag(\"frequency_list\", f_1);\n\ntype_tag(my_frequency_1);\n```\n\n```javascript\nUsing\n\ttype_tag,\n```\n\nwe can define predicates is_rectangular and is_polar, which recognize rectangular and polar numbers, respectively:\n\n```javascript\nrectangular_or_polar\n rectangular_or_polar_example\n attach_tag\n\nfunction is_rectangular(z) {\n return type_tag(z) === \"rectangular\";\n}\nfunction is_polar(z) {\n return type_tag(z) === \"polar\";\n}\n```\n\nWith type tags, Ben and Alyssa can now modify their code so that their two\ndifferent representations can coexist in the same system.\n\nWhenever Ben\nconstructs a complex number, he tags it as rectangular.\n\nWhenever Alyssa\nconstructs a complex number, she tags it as polar.\n\nIn addition, Ben and\nAlyssa must make sure that the names of their\nfunctions\ndo not conflict.\n\nOne way to do this is for Ben to append the suffix\nfunctions\nand for Alyssa to append s revised rectangular representation from\nsection :\n\n```javascript\nmake_complex_number_rectangular\n attach_tag\n square_definition\n make_complex_number_rectangular_example\n 1.932653061713073\n\nfunction real_part_rectangular(z) { return head(z); }\n\nfunction imag_part_rectangular(z) { return tail(z); }\n\nfunction magnitude_rectangular(z) {\n return math_sqrt(square(real_part_rectangular(z)) +\n square(imag_part_rectangular(z)));\n}\nfunction angle_rectangular(z) {\n return math_atan2(imag_part_rectangular(z),\n real_part_rectangular(z));\n}\nfunction make_from_real_imag_rectangular(x, y) {\n return attach_tag(\"rectangular\", pair(x, y));\n}\nfunction make_from_mag_ang_rectangular(r, a) {\n return attach_tag(\"rectangular\",\n pair(r * math_cos(a), r * math_sin(a)));\n}\n```\n\n```javascript\nmake_complex_number_rectangular_example\n\nconst bens_co_num = make_from_mag_ang_rectangular(\n 3.0, 0.7);\n\nimag_part_rectangular(contents(bens_co_num));\n```\n\nand here is Alyssa s revised polar representation:", + "token_count": 269, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": "Tagged data", "chunk_index": 2, - "content": "Recursion length length procedure function of section with the count-leaves count_leaves procedure, function, which returns the total number of leaves of a tree: tree_x (define x (cons (list 1 2) (list 3 4))) const x = pair(list(1, 2), list(3, 4)); length_tree_x tree_x 3 (length x) 3 length(x); 3 count_leaves_tree_x tree_x count_leaves 4 (count-leaves x) 4 count_leaves(x); 4 list_x_x tree_x 3 (list x x) (((1 2) 3 4) ((1 2) 3 4)) list(x, x); length(head(tail(list(x, x)))); list(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4)) length_list_x_x tree_x 2 (length (list x x)) 2 length(list(x, x)); 2 count_leaves_list_x_x tree_x count_leaves 8 (count-leaves (list x x)) 8 count_leaves(list(x, x)); 8\nTo implement count-leaves , count_leaves , recall the recursive plan for computing length : length : Length The length of a list x is 1 plus length the length of the cdr tail of x . Length The length of the empty list is 0. Count-leaves The function count_leaves is similar. The value for the empty list is the same: Count-leaves count_leaves of the empty list is 0. But in the reduction step, where we strip off the car head of the list, we must take into account that the car head may itself be a tree whose leaves we need to count. Thus, the appropriate reduction step is Count-leaves count_leaves of a tree x is count-leaves count_leaves of the car head of x plus count-leaves count_leaves of the cdr tail of x . Finally, by taking car s head s we reach actual leaves, so we need another base case: Count-leaves count_leaves of a leaf is 1. To aid in writing recursive procedures functions on trees, Scheme our JavaScript environment provides the primitive predicate pair? , is_pair , which tests whether its argument is a pair. Here is the complete procedure: function: count_leaves 4 (define (count-leaves x) (cond ((null? x) 0) ((not (pair? x)) 1) (else (+ (count-leaves (car x)) (count-leaves (cdr x)))))) function count_leaves(x) { return is_null(x) ? 0 : ! is_pair(x) ? 1 : count_leaves(head(x)) + count_leaves(tail(x)); } (count-leaves (cons (list 1 2) (list 3 4))) count_leaves(pair(list(1, 2), list(3, 4)));", - "token_count": 627, - "source_files": [ - "subsection2.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Tagged_data_2" }, { - "chapter_file": "chapter2_chunks.json", - "section": "Hierarchical Data and the Closure Property", - "subsection": "Hierarchical Structures", - "chunk_id": "chapter2_chunks_Hierarchical_Structures_3", + "content": "and here is Alyssa s revised polar representation:\n\n```javascript\nmake_complex_number_polar\n attach_tag\n square_definition\n make_complex_number_polar_example\n 1.932653061713073\n\nfunction real_part_polar(z) {\n return magnitude_polar(z) * math_cos(angle_polar(z));\n}\nfunction imag_part_polar(z) {\n return magnitude_polar(z) * math_sin(angle_polar(z));\n}\nfunction magnitude_polar(z) { return head(z); }\n\nfunction angle_polar(z) { return tail(z); }\n\nfunction make_from_real_imag_polar(x, y) {\n return attach_tag(\"polar\",\n pair(math_sqrt(square(x) + square(y)),\n math_atan2(y, x)));\n}\n\nfunction make_from_mag_ang_polar(r, a) {\n return attach_tag(\"polar\", pair(r, a));\n}\n```\n\n```javascript\nmake_complex_number_polar_example\n\nconst alyssas_co_num = make_from_mag_ang_polar(\n 3.0, 0.7);\n\nimag_part_polar(contents(alyssas_co_num));\n```\n\nfunction\nthat checks the tag of its argument and calls the appropriate\nfunction\nfor handling data of that type.\n\nFor example, to obtain the real part of\na complex number,\nreal_part\nexamines the tag to determine whether to use Ben s\nreal_part_rectangular\nor Alyssa s\nreal_@part_@polar.\n\nIn either case, we use\nfunction\nas required:\n\n```javascript\nmake_complex_number\n rectangular_or_polar\n make_complex_number_rectangular\n make_complex_number_polar\n make_complex_number_example_2\n 1.932653061713073\n\nfunction real_part(z) {\n return is_rectangular(z)\n ? real_part_rectangular(contents(z))\n : is_polar(z)\n ? real_part_polar(contents(z))\n : error(z, \"unknown type -- real_part\");\n}\nfunction imag_part(z) {\n return is_rectangular(z)\n ? imag_part_rectangular(contents(z))\n : is_polar(z)\n ? imag_part_polar(contents(z))\n : error(z, \"unknown type -- imag_part\");\n}\nfunction magnitude(z) {\n return is_rectangular(z)\n ? magnitude_rectangular(contents(z))\n : is_polar(z)\n ? magnitude_polar(contents(z))\n : error(z, \"unknown type -- magnitude\");\n}\nfunction angle(z) {\n return is_rectangular(z)\n ? angle_rectangular(contents(z))\n : is_polar(z)\n ? angle_polar(contents(z))\n : error(z, \"unknown type -- angle\");\n}\n```\n\n```javascript\nmake_complex_number_example_2\n\nconst alyssas_co_num = make_from_mag_ang_polar(\n 3.0, 0.7);\n\nimag_part(alyssas_co_num);\n```\n\nTo implement the complex-number arithmetic operations, we can use the same\nfunctions\nadd_complex,\nsub_complex,\nmul_complex,\nand\ndiv_complex\nfrom section ,\nbecause the selectors they call are generic, and so will work with either\nrepresentation.\n\nFor example, the\nfunction\nadd_complex\nis still\n\n```javascript\nfunction add_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) + real_part(z2),\n imag_part(z1) + imag_part(z2));\n}\n```\n\nFinally, we must choose whether to construct complex numbers using\nBen s representation or Alyssa s representation.", + "token_count": 290, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": "Tagged data", "chunk_index": 3, - "content": "Just as map is a powerful abstraction for dealing with sequences, map together with recursion is a powerful abstraction for dealing with trees. For instance, the scale-tree scale_tree procedure, function, analogous to scale-list scale_list of section , takes as arguments a numeric factor and a tree whose leaves are numbers. It returns a tree of the same shape, where each number is multiplied by the factor. The recursive plan for scale-tree scale_tree is similar to the one for count-leaves : count_leaves : scale_tree 10 (define (scale-tree tree factor) (cond ((null? tree) nil) ((not (pair? tree)) (* tree factor)) (else (cons (scale-tree (car tree) factor) (scale-tree (cdr tree) factor))))) function scale_tree(tree, factor) { return is_null(tree) ? null : ! is_pair(tree) ? tree * factor : pair(scale_tree(head(tree), factor), scale_tree(tail(tree), factor)); } scale_tree (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) (10 (20 (30 40) 50) (60 70)) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); list(10, list(20, list(30, 40), 50), list(60, 70))", - "token_count": 336, - "source_files": [ - "subsection2.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Tagged_data_3" }, { - "chapter_file": "chapter2_chunks.json", - "section": "Hierarchical Data and the Closure Property", - "subsection": "Hierarchical Structures", - "chunk_id": "chapter2_chunks_Hierarchical_Structures_4", + "content": "Finally, we must choose whether to construct complex numbers using\nBen s representation or Alyssa s representation.\n\nOne\nreasonable choice is to construct rectangular numbers whenever we have\nreal and imaginary parts and to construct polar numbers whenever we have\nmagnitudes and angles:\n\n```javascript\nmake_complex_number_generic\n make_complex_number\n make_complex_number_rectangular\n make_complex_number_polar\n make_complex_number_generic_example\n 1.932653061713073\n\nfunction make_from_real_imag(x, y) {\n return make_from_real_imag_rectangular(x, y);\n}\nfunction make_from_mag_ang(r, a) {\n return make_from_mag_ang_polar(r, a);\n}\n```\n\n```javascript\nmake_complex_number_generic_example\n\nconst alyssas_co_num = make_from_mag_ang(\n 3.0, 0.7);\n\nimag_part(alyssas_co_num);\n```\n\nStructure\n$\\!$ The resulting complex-number system has the structure shown in\nfigure.\n\nThe system has been decomposed into three relatively independent parts: the\ncomplex-number-arithmetic operations, Alyssa s polar implementation,\nand Ben s rectangular implementation.\n\nThe polar and rectangular\nimplementations could have been written by Ben and Alyssa working\nseparately, and both of these can be used as underlying representations by\na third programmer implementing the complex-arithmetic\nfunctions\nin terms of the abstract constructor/selector interface.\n\nSince each data object is tagged with its type, the selectors operate on\nthe data in a\ns polar\npackage) a complex number is an untyped pair (magnitude, angle).\n\nWhen a\ngeneric selector operates on a number of s\ncode.\n\nConversely, when Alyssa constructs a number for general use, she\ntags it with a type so that it can be appropriately recognized by the\nhigher-level\nfunctions.\n\nThis discipline of stripping off and attaching tags as data objects are\npassed from level to level can be an important organizational strategy,\nas we shall see in section.", + "token_count": 246, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Multiple Representations for Abstract Data", + "subsection": "Tagged data", "chunk_index": 4, - "content": "Another way to implement scale-tree scale_tree is to regard the tree as a sequence of sub-trees and use map . map . We map over the sequence, scaling each sub-tree in turn, and return the list of results. In the base case, where the tree is a leaf, we simply multiply by the factor: (scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10) scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10); head(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)), 10)); scale_tree_with_map 10 (define (scale-tree tree factor) (map (lambda (sub-tree) (if (pair? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree)) function scale_tree(tree, factor) { return map(sub_tree => is_pair(sub_tree) ? scale_tree(sub_tree, factor) : sub_tree * factor, tree); } Many tree operations can be implemented by similar combinations of sequence operations and recursion.", - "token_count": 253, - "source_files": [ - "subsection2.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Tagged_data_4" }, { - "chapter_file": "chapter3_chunks.json", - "section": null, - "subsection": "Modularity, Objects, and State", - "chunk_id": "chapter3_chunks_Modularity,_Objects,_and_State_1", + "content": "This section provides practice in the use of list structure and data\nabstraction to manipulate sets and trees.\n\nThe application is to\nmethods for representing data as sequences of ones and zeros (bits).\n\nFor example, the\n$2^7$ , or\n128, possible different characters.\n\nIn general, if we want to distinguish\n$n$ different symbols, we will need to use\n$\\log_2 n$ bits per symbol.\n\nIf all our messages\nare made up of the eight symbols A, B, C, D, E, F, G, and H, we can choose\na code with three bits per character, for example\nA\n000\nC\n010\nE\n100\nG\n110\nB\n001\nD\n011\nF\n101\nH\n111\nWith this code, the message\nBACADAEAFABBAAAGAH\nis encoded as the string of 54 bits\n001000010000011000100000101000001001000000000110000111\n\nCodes such as ASCII and the A-through-H code above are known as\nfixed-length codes, because they represent each symbol in the\nmessage with the same number of bits.\n\nIt is sometimes advantageous to use\nvariable-length codes, in which different symbols may be\nrepresented by different numbers of bits.\n\nFor example,\nA\n0\nC\n1010\nE\n1100\nG\n1110\nB\n100\nD\n1011\nF\n1101\nH\n1111\nWith this code, the same message as above is encoded as the string\n100010100101101100011010100100000111001111\nThis string contains 42 bits, so it saves more than 20% in space in\ncomparison with the fixed-length code shown above.\n\nOne of the difficulties of using a variable-length code is knowing\nwhen you have reached the end of a symbol in reading a sequence of\nzeros and ones.\n\nMorse code solves this problem by using a special\nseparator code (in this case, a pause) after the sequence of\ndots and dashes for each letter.", + "token_count": 281, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Huffman Encoding Trees", "chunk_index": 1, - "content": "The preceding chapters introduced the basic elements from which programs are made. We saw how primitive procedures functions and primitive data are combined to construct compound entities, and we learned that abstraction is vital in helping us to cope with the complexity of large systems. But these tools are not sufficient for designing programs. Effective program synthesis also requires organizational principles that can guide us in formulating the overall design of a program. In particular, we need strategies to help us structure large systems so that they will be modular , that is, so that they can be divided naturally into coherent parts that can be separately developed and maintained. One powerful design strategy, which is particularly appropriate to the construction of programs for\nTo a large extent, then, the way we organize a large program is dictated by our perception of the system to be modeled. In this chapter we will investigate two prominent organizational strategies arising from two rather different world views of the structure of systems. The first organizational strategy concentrates on objects , viewing a large system as a collection of distinct objects whose behaviors may change over time. An alternative organizational strategy concentrates on the streams of information that flow in the system, much as an electrical engineer views a signal-processing system. Both the object-based approach and the stream-processing approach raise significant linguistic issues in programming. With objects, we must be concerned with how a computational object can change and yet maintain its identity.", - "token_count": 273, - "source_files": [ - "chapter3.xml" - ] + "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_1" + }, + { + "content": "Morse code solves this problem by using a special\nseparator code (in this case, a pause) after the sequence of\ndots and dashes for each letter.\n\nAnother solution is to design the\ncode in such a way that no complete code for any symbol is the\nbeginning (or prefix ) of the code for another symbol.\n\nSuch a\ncode is called a\nprefix code.\n\nIn the example above, A is encoded by 0 and B is\nencoded by 100, so no other symbol can have a code that begins with 0 or\nwith 100.\n\nIn general, we can attain significant savings if we use\nvariable-length prefix codes that take advantage of the relative\nfrequencies of the symbols in the messages to be encoded.\n\nOne\nparticular for doing this is called the Huffman encoding\nmethod, after its discoverer,\n\nA Huffman encoding tree.\n\nFigure shows the Huffman tree for the\nA-through-H code given above.\n\nThe weights at the leaves indicate that the\ntree was designed for messages in which A appears with relative frequency\n8, B with relative frequency 3, and the other letters each with relative\nfrequency 1.\n\nGiven a Huffman tree, we can find the encoding of any symbol by\nstarting at the root and moving down until we reach the leaf that\nholds the symbol.\n\nEach time we move down a left branch we add a 0 to\nthe code, and each time we move down a right branch we add a 1.", + "token_count": 244, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Huffman Encoding Trees", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_2" + }, + { + "content": "Each time we move down a left branch we add a 0 to\nthe code, and each time we move down a right branch we add a 1.\n\n(We\ndecide which branch to follow by testing to see which branch either is\nthe leaf node for the symbol or contains the symbol in its set.) For\nexample, starting from the root of the tree in\nfigure , we arrive at the leaf for D by\nfollowing a right branch, then a left branch, then a right branch, then a\nright branch; hence, the code for D is 1011.\n\nTo decode a bit sequence using a Huffman tree, we begin at the root\nand use the successive zeros and ones of the bit sequence to determine\nwhether to move down the left or the right branch.\n\nEach time we come\nto a leaf, we have generated a new symbol in the message, at which\npoint we start over from the root of the tree to find the next symbol.\n\nFor example, suppose we are given the tree above and the sequence\n10001010.\n\nStarting at the root, we move down the right branch (since\nthe first bit of the string is 1), then down the left branch (since\nthe second bit is 0), then down the left branch (since the third bit\nis also 0).\n\nThis brings us to the leaf for B, so the first\nsymbol of the decoded message is B.\n\nNow we start again at the root,\nand we make a left move because the next bit in the string is 0.\n\nThis brings us to the leaf for A.\n\nThen we start again at the root\nwith the rest of the string 1010, so we move right, left, right, left and\nreach C.", + "token_count": 295, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Huffman Encoding Trees", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_3" + }, + { + "content": "Then we start again at the root\nwith the rest of the string 1010, so we move right, left, right, left and\nreach C.\n\nThus, the entire message is BAC.\n\nGiven an alphabet of symbols and their relative frequencies,\nhow do we construct the best code?\n\n(In other words, which\ntree will encode messages with the fewest bits?) Huffman gave an algorithm\nfor doing this and showed that the resulting code is indeed the best\nvariable-length code for messages where the relative frequency of the\nsymbols matches the frequencies with which the code was constructed.\n\nThe algorithm for generating a Huffman tree is very simple.\n\nThe idea\nis to arrange the tree so that the symbols with the lowest frequency\nappear farthest away from the root.\n\nBegin with the set of leaf nodes,\ncontaining symbols and their frequencies, as determined by the initial data\nfrom which the code is to be constructed.\n\nNow find two leaves with\nthe lowest weights and merge them to produce a node that has these\ntwo nodes as its left and right branches.\n\nThe weight of the new node\nis the sum of the two weights.\n\nRemove the two leaves from the\noriginal set and replace them by this new node.\n\nNow continue this\nprocess.\n\nAt each step, merge two nodes with the smallest weights,\nremoving them from the set and replacing them with a node that has\nthese two as its left and right branches.\n\nThe process stops when\nthere is only one node left, which is the root of the entire tree.", + "token_count": 259, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Huffman Encoding Trees", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_4" + }, + { + "content": "The process stops when\nthere is only one node left, which is the root of the entire tree.\n\nHere is how the Huffman tree of figure was\ngenerated:\nInitial leaves\n$\\{$ (A 8) (B 3) (C 1) (D 1) (E 1) (F 1) (G 1) (H 1) $\\}$\nMerge\n$\\{$ (A 8) (B 3) ( $\\{$ C D $\\}$ 2) (E 1) (F 1) (G 1) (H 1) $\\}$\nMerge\n$\\{$ (A 8) (B 3) ( $\\{$ C D $\\}$ 2) ( $\\{$ E F $\\}$ 2) (G 1) (H 1) $\\}$\nMerge\n$\\{$ (A 8) (B 3) ( $\\{$ C D $\\}$ 2) ( $\\{$ E F $\\}$ 2) ( $\\{$ G H $\\}$ 2) $\\}$\nMerge\n$\\{$ (A 8) (B 3) ( $\\{$ C D $\\}$ 2) ( $\\{$ E F G H $\\}$ 4) $\\}$\nMerge\n$\\{$ (A 8) ( $\\{$ B C D $\\}$ 5) ( $\\{$ E F G H $\\}$ 4) $\\}$\nMerge\n$\\{$ (A 8) ( $\\{$ B C D E F G H $\\}$ 9) $\\}$\nFinal merge\n$\\{$ ( $\\{$ A B C D E F G H $\\}$ 17) $\\}$\nThe algorithm does not always specify a unique tree, because there may\nnot be unique smallest-weight nodes at each step.\n\nAlso, the choice of\nthe order in which the two nodes are merged (i.e., which will be the\nright branch and which will be the left branch) is arbitrary.\n\nIn the exercises below we will work with a system that uses\nHuffman trees to encode and decode messages and generates Huffman\ntrees according to the algorithm outlined above.\n\nWe will begin by\ndiscussing how trees are represented.\n\nLeaves of the tree are represented by a list consisting of the string \"leaf\", the symbol at the leaf, and the weight:", + "token_count": 299, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Huffman Encoding Trees", + "chunk_index": 5, + "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_5" + }, + { + "content": "Leaves of the tree are represented by a list consisting of the string \"leaf\", the symbol at the leaf, and the weight:\n\n```javascript\nmake_leaf\n make_leaf_example\n 8\n\nfunction make_leaf(symbol, weight) {\n return list(\"leaf\", symbol, weight);\n}\nfunction is_leaf(object) {\n return head(object) === \"leaf\";\n}\nfunction symbol_leaf(x) { return head(tail(x)); }\n\nfunction weight_leaf(x) { return head(tail(tail(x))); }\n```\n\n```javascript\nmake_leaf_example\n\nconst my_leaf = make_leaf(\"A\", 8);\n\nweight_leaf(my_leaf);\n```\n\nA general tree will be a list of\na string \"code_tree\",\na left branch, a right branch, a set\nof symbols, and a weight.\n\nThe set of symbols will be simply a list of\nthe symbols, rather than some more sophisticated set representation.\n\nWhen we make a tree by merging two nodes, we obtain the weight of the\ntree as the sum of the weights of the nodes, and the set of symbols as\nthe union of the sets of symbols for the nodes.\n\nSince our symbol sets are\nrepresented as lists, we can form the union by using the\nfunction\nwe defined in section :\n\n```javascript\nmake_code_tree\n make_code_tree_example\n [ 'leaf', [ 'A', [ 8, null ] ] ]\n tree_property\n\nfunction make_code_tree(left, right) {\n return list(\"code_tree\", left, right,\n append(symbols(left), symbols(right)),\n weight(left) + weight(right));\n}\n```\n\n```javascript\nmake_code_tree_example\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nmake_code_tree(my_leaf_1, my_leaf_2);\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nhead(tail(make_code_tree(my_leaf_1, my_leaf_2)));\n```\n\nIf we make a tree in this way, we have the following selectors:\n\n```javascript\ntree_property\n make_leaf\n tree_property_example\n 11\n\nfunction left_branch(tree) { return head(tail(tree)); }\n\nfunction right_branch(tree) { return head(tail(tail(tree))); }\n\nfunction symbols(tree) {\n return is_leaf(tree)\n ? list(symbol_leaf(tree))\n : head(tail(tail(tail(tree))));\n}\nfunction weight(tree) {\n return is_leaf(tree)\n ? weight_leaf(tree)\n : head(tail(tail(tail(tail(tree)))));\n}\n```\n\n```javascript\ntree_property_example\n make_code_tree\n make_leaf\n tree_property_example\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\nconst my_tree = make_code_tree(my_leaf_1, my_leaf_2);\n\nweight(my_tree);\n```", + "token_count": 300, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Huffman Encoding Trees", + "chunk_index": 6, + "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_6" + }, + { + "content": "If we make a tree in this way, we have the following selectors:\n\nThe functions generic functions (functions that can handle more than one kind of data), which we will have much more to say about in sections\n\nand.\n\nThe following\nfunction\nimplements the decoding algorithm.\n\nIt takes as arguments a list of zeros\nand ones, together with a Huffman tree.\n\n```javascript\ndecode_function\n make_leaf\n make_code_tree\n tree_property\n decode_function_example\n [ 'B', [ 'B', [ 'A', null ] ] ]\n\nfunction decode(bits, tree) {\n function decode_1(bits, current_branch) {\n if (is_null(bits)) {\n return null;\n } else {\n const next_branch = choose_branch(head(bits),\n current_branch);\n return is_leaf(next_branch)\n ? pair(symbol_leaf(next_branch),\n decode_1(tail(bits), tree))\n : decode_1(tail(bits), next_branch);\n }\n }\n return decode_1(bits, tree);\n}\n\nfunction choose_branch(bit, branch) {\n return bit === 0\n ? left_branch(branch)\n : bit === 1\n ? right_branch(branch)\n : error(bit, \"bad bit -- choose_branch\");\n}\n```\n\n```javascript\ndecode_function_example\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nconst my_tree = make_code_tree(my_leaf_1, my_leaf_2);\n\ndecode(list(0, 1, 1, 0), my_tree);\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nconst my_tree = make_code_tree(my_leaf_1, my_leaf_2);\n\ntail(decode(list(0, 1, 1, 0), my_tree));\n```\n\nThe\nfunction\ndecode_1\ntakes two arguments: the list of remaining bits and the current position in\nthe tree.\n\nIt keeps moving down the tree, choosing a left or\na right branch according to whether the next bit in the list is a zero or a\none.\n\n(This is done with the\nfunction\nchoose_branch.)\nWhen it reaches a leaf, it returns the symbol at that leaf as the next\nsymbol in the message by\n\n```javascript\nadjoining\n\tit to the result of decoding the rest of the message,\n\tstarting at the root of the tree.\n```\n\nNote the error check in the final clause of choose_branch, which complains if the function finds something other than a zero or a one in\n\nthe input data.", + "token_count": 302, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Huffman Encoding Trees", + "chunk_index": 7, + "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_7" + }, + { + "content": "the input data.\n\nIn our representation of trees, each non-leaf node contains a set of\nsymbols, which we have represented as a simple list.\n\nHowever, the\ntree-generating algorithm discussed above requires that we also work\nwith sets of leaves and trees, successively merging the two smallest\nitems.\n\nSince we will be required to repeatedly find the smallest item\nin a set, it is convenient to use an ordered representation for this\nkind of set.\n\nWe will represent a set of leaves and trees as a list of elements,\narranged in increasing order of weight.\n\nThe following\nadjoin_set\nfunction\nfor constructing sets is similar to the one\ndescribed in exercise ; however, items\nare compared by their weights, and the element being added to the set is\nnever already in it.\n\n```javascript\nadjoin_set3\n tree_property\n adjoin_set3_example\n [ 'leaf', [ 'B', [ 3, null ] ] ]\n\nfunction adjoin_set(x, set) {\n return is_null(set)\n ? list(x)\n : weight(x) < weight(head(set))\n ? pair(x, set)\n : pair(head(set), adjoin_set(x, tail(set)));\n}\n```\n\n```javascript\nadjoin_set3_example\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nadjoin_set(my_leaf_1, adjoin_set(my_leaf_2, null));\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nhead(adjoin_set(my_leaf_1, adjoin_set(my_leaf_2, null)));\n```\n\nThe following function takes a list of symbol-frequency pairs such as list(list(\"A\", 4), list(\"B\", 2), list(\"C\", 1), list(\"D\", 1)) and constructs an initial ordered set\n\nof leaves, ready to be merged according to the Huffman algorithm:\n\n```javascript\nmake_leaf_set\n make_leaf\n adjoin_set3\n make_leaf_set_example\n [ 'leaf', [ 'leaf', [ 'A', null ] ] ]\n\nfunction make_leaf_set(pairs) {\n if (is_null(pairs)) {\n return null;\n } else {\n const first_pair = head(pairs);\n return adjoin_set(\n make_leaf(head(first_pair), // symbol\n head(tail(first_pair))), // frequency\n make_leaf_set(tail(pairs)));\n }\n}\n```\n\n```javascript\nmake_leaf_set_example\n\nmake_leaf_set( list( list(\"A\", 4),\n list(\"B\", 2),\n list(\"C\", 1),\n list(\"D\", 1) ) );\n\nhead(make_leaf_set( list( list(\"A\", 4),\n list(\"B\", 2),\n list(\"C\", 1),\n list(\"D\", 1) ) ) );\n```", + "token_count": 300, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Huffman Encoding Trees", + "chunk_index": 8, + "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_8" + }, + { + "content": "of leaves, ready to be merged according to the Huffman algorithm:\n\nThe tree will be unbalanced, similar to the tree given in\nfigure.\n\nEncoding the most\nfrequent symbol requires one bit, whereas\n$n - 1$ bits are required to encode the\nleast frequent symbol.", + "token_count": 44, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Huffman Encoding Trees", + "chunk_index": 9, + "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_9" + }, + { + "content": "In the previous examples we built representations for two kinds of\ncompound data objects: rational numbers and algebraic expressions.\n\nIn\none of these examples we had the choice of simplifying (reducing) the\nexpressions at either construction time or selection time, but other\nthan that the choice of a representation for these structures in terms\nof lists was straightforward.\n\nWhen we turn to the representation of\nsets, the choice of a representation is not so obvious.\n\nIndeed, there\nare a number of possible representations, and they differ\nsignificantly from one another in several ways.\n\nInformally, a set is simply a collection of distinct objects.\n\nTo give\na more precise definition we can employ the method of data\nabstraction.\n\nThat is, we define set by specifying the\nunion_set,\nintersection_set,\nis_element_of_set,\nand\nadjoin_set.\n\nThe function is_@element_of_set\nis a predicate that determines whether a given element is a member of a set.\n\nThe function adjoin_@set\ntakes an object and a set as arguments and returns a set that contains the\nelements of the original set and also the adjoined element.\n\nThe function union_@set\ncomputes the union of two sets, which is the set containing each element\nthat appears in either argument.\n\n```javascript\nThe function\n intersection_@set\n```\n\ncomputes the intersection of two sets, which is the set containing only\nelements that appear in both arguments.\n\nFrom the viewpoint of data\nabstraction, we are free to design any representation that implements these\noperations in a way consistent with the interpretations given\nabove.\n\nOne way to represent a set is as a list of its elements in which no\nelement appears more than once.\n\nThe empty set is represented by the\nempty list.\n\nIn this representation,\nis_element_of_set\nis similar to the\nfunction\n\n```javascript\nmember\n of section.\n```\n\nIt uses equal instead of\n\n```javascript\n===\n```", + "token_count": 298, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Representing Sets", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_1" + }, + { + "content": "It uses equal instead of\n\nso that the set elements need not be just numbers or strings:\n\n```javascript\nis_element_of_set\n is_element_of_set_example\n true\n\nfunction is_element_of_set(x, set) {\n return is_null(set)\n ? false\n : equal(x, head(set))\n ? true\n : is_element_of_set(x, tail(set));\n}\n```\n\n```javascript\nis_element_of_set_example\n adjoin_set\n\nis_element_of_set(15,\n adjoin_set(10, adjoin_set(15, adjoin_set(20, null))));\n```\n\nUsing this, we can write\nadjoin_set.\n\nIf the object to be adjoined is already in the set, we just return the set.\n\nOtherwise, we use\npair\nto add the object to the list that represents the set:\n\n```javascript\nadjoin_set\n is_element_of_set\n adjoin_set_example\n [ 10, [ 15, [ 20, null ] ] ]\n\nfunction adjoin_set(x, set) {\n return is_element_of_set(x, set)\n ? set\n : pair(x, set);\n}\n```\n\n```javascript\nadjoin_set_example\n\nadjoin_set(10, adjoin_set(15, adjoin_set(20, null)));\n```\n\nFor\nintersection_set\nwe can use a recursive strategy.\n\nIf we know how to form the intersection\nof\ntail\nof\nhead\nof\nhead(set1)\nis also in\nfunction:\n\n```javascript\nintersection_set\n is_element_of_set\n intersection_set_example\n [ 10, [ 20, null ] ]\n\nfunction intersection_set(set1, set2) {\n return is_null(set1) || is_null(set2)\n ? null\n : is_element_of_set(head(set1), set2)\n ? pair(head(set1), intersection_set(tail(set1), set2))\n : intersection_set(tail(set1), set2);\n}\n```\n\n```javascript\nintersection_set_example\n adjoin_set\n intersection_set\n\nintersection_set(\n adjoin_set(10, adjoin_set(20, adjoin_set(30, null))),\n adjoin_set(10, adjoin_set(15, adjoin_set(20, null))));\n```\n\nIn designing a representation, one of the issues we should be concerned\nwith is efficiency.\n\nConsider the number of steps required by our set\noperations.\n\nSince they all use\nis_element_of_set,\nthe speed of this operation has a major impact on the efficiency of the set\nimplementation as a whole.\n\nNow, in order to check whether an object is a\nmember of a set,\nis_element_of_set\nmay have to scan the entire set.\n\n(In the worst case, the object turns out\nnot to be in the set.) Hence, if the set has\n$n$ elements,\nis_element_of_set\nmight take up to $n$ steps.\n\nThus, the number of\nsteps required grows as $\\Theta(n)$.", + "token_count": 301, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Representing Sets", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_2" + }, + { + "content": "Thus, the number of\nsteps required grows as $\\Theta(n)$.\n\nThe number\nof steps required by\nadjoin_set,\nwhich uses\nthis operation, also grows as $\\Theta(n)$.\n\nFor\nintersection_set,\nwhich does an\nis_element_of_set\ncheck for each element of $\\Theta(n^{2})$ for two sets of size\n$n$.\n\nThe same will be true of\nunion_set.\n\nOne way to speed up our set operations is to change the representation\nso that the set elements are listed in increasing order.\n\nTo do this,\nwe need some way to compare two objects so that we can say which is\nbigger.\n\nFor example, we could compare\nstrings\nlexicographically, or\nwe could agree on some method for assigning a unique number to an\nobject and then compare the elements by comparing the corresponding\nnumbers.\n\nTo keep our discussion simple, we will consider only the\ncase where the set elements are numbers, so that we can compare\nelements using $\\{1,3,6,10\\}$ by listing the elements in any\norder, our new representation allows only the list\nlist(1, 3, 6, 10).\n\nOne advantage of ordering shows up in\nis_element_of_set:\nIn checking for the presence of an item, we no longer have to scan the\nentire set.\n\nIf we reach a set element that is larger than the item we\nare looking for, then we know that the item is not in the set:\n\n```javascript\nx\n is_element_of_set2\n is_element_of_set_example_2\n true\n\nfunction is_element_of_set(x, set) {\n return is_null(set)\n ? false\n : x === head(set)\n ? true\n : x < head(set)\n ? false\n : // $\\texttt{x > head(set)}$\n is_element_of_set(x, tail(set));\n}\n```\n\n```javascript\nis_element_of_set_example_2\n\nis_element_of_set(15, list(10, 15, 20));\n```\n\nHow many steps does this save?\n\nIn the worst case, the item we are\nlooking for may be the largest one in the set, so the number of steps\nis the same as for the unordered representation.", + "token_count": 296, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Representing Sets", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_3" + }, + { + "content": "In the worst case, the item we are\nlooking for may be the largest one in the set, so the number of steps\nis the same as for the unordered representation.\n\nOn the other hand,\nif we search for items of many different sizes we can expect that\nsometimes we will be able to stop searching at a point near the\nbeginning of the list and that other times we will still need to\nexamine most of the list.\n\nOn the average we should expect to have to\nexamine about half of the items in the set.\n\nThus, the average\nnumber of steps required will be about $n/2$.\n\nThis is still $\\Theta(n)$ growth, but\nit does save us, on the average, a factor of 2 in number of steps over the\nprevious implementation.\n\nWe obtain a more impressive speedup with\nintersection_set.\n\nIn the unordered representation this operation required\n$\\Theta(n^2)$ steps, because we performed a\ncomplete scan of\ntails\nof the two sets.\n\nSuppose, however, that\ntail\nof\ntail\nof\nfunction:\n\n```javascript\nintersection_set_ordered\n intersection_set_example2\n [ 10, [ 20, null ] ]\n\nfunction intersection_set(set1, set2) {\n if (is_null(set1) || is_null(set2)) {\n return null;\n } else {\n const x1 = head(set1);\n const x2 = head(set2);\n return x1 === x2\n ? pair(x1, intersection_set(tail(set1), tail(set2)))\n : x1 < x2\n ? intersection_set(tail(set1), set2)\n : // $\\texttt{x2 < x1}$\n\t intersection_set(set1, tail(set2));\n }\n}\n```\n\n```javascript\nintersection_set_example2\n intersection_set_ordered\n\nintersection_set(\n list(10, 20, 30),\n list(10, 15, 20));\n```\n\nTo estimate the number of steps required by this process, observe that at each step we reduce the intersection problem to computing intersections of smaller\n\nsets removing the first element from $\\Theta(n)$ growth rather than $\\Theta(n^2)$ a considerable speedup, even for sets of moderate size.\n\nWe can do better than the ordered-list representation by arranging the set\nelements in the form of a tree.", + "token_count": 302, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Representing Sets", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_4" + }, + { + "content": "We can do better than the ordered-list representation by arranging the set\nelements in the form of a tree.\n\nEach node of the tree holds one element of\nthe set, called the entry at that node, and a link to each\nof two other (possibly empty) nodes.\n\nThe left link points to\nelements smaller than the one at the node, and the right\nlink to elements greater than the one at the node.\n\nFigure shows some trees that represent\nthe set $\\{1,3,5,7,9,11\\}$.\n\nThe same set may be\nrepresented by a tree in a number of different ways.\n\nThe only thing we\nrequire for a valid representation is that all elements in the left subtree\nbe smaller than the node entry and that all elements in the right subtree be\nlarger.\n\nVarious binary trees that represent the set\n$\\{ 1,3,5,7,9,11 \\}$.\n\nThe advantage of the tree representation is this: Suppose we want to check\nwhether a number $x$ is contained in a set.\n\nWe\nbegin by comparing $x$ with the entry in the\ntop node.\n\nIf $x$ is less than this, we know\nthat we need only search the left subtree; if $x$\nis greater, we need only search the right subtree.\n\nNow, if the tree is\nbalanced, each of these subtrees will be about half the size\nof the original.\n\nThus, in one step we have reduced the problem of\nsearching a tree of size $n$ to searching a tree\nof size $n/2$.\n\nSince the size of the tree is\nhalved at each step, we should expect that the number of steps needed to\nsearch a tree of size $n$ grows as\n$\\Theta(\\log n)$.\n\nWe can represent trees by using functions :", + "token_count": 282, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Representing Sets", + "chunk_index": 5, + "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_5" + }, + { + "content": "We can represent trees by using functions :\n\n```javascript\nmake_tree_function\n make_tree_example\n 20\n\nfunction entry(tree) { return head(tree); }\n\nfunction left_branch(tree) { return head(tail(tree)); }\n\nfunction right_branch(tree) { return head(tail(tail(tree))); }\n\nfunction make_tree(entry, left, right) {\n return list(entry, left, right);\n}\n```\n\n```javascript\nmake_tree_example\n\nentry(\n left_branch(\n right_branch(\n make_tree(10,\n null,\n make_tree(30,\n make_tree(20, null, null),\n null)))));\n```\n\nNow we can write is_element_of_set using the strategy described above:\n\n```javascript\nmake_tree_function\n is_element_of_set_example_3\n true\n\nfunction is_element_of_set(x, set) {\n return is_null(set)\n ? false\n : x === entry(set)\n ? true\n : x < entry(set)\n ? is_element_of_set(x, left_branch(set))\n : // $\\texttt{x > entry(set)}$\n is_element_of_set(x, right_branch(set));\n}\n```\n\n```javascript\nis_element_of_set_example_3\n\nis_element_of_set(20,\n make_tree(10,\n null,\n make_tree(30,\n make_tree(20, null, null),\n null)));\n```\n\nAdjoining an item to a set is implemented similarly and also requires\n$\\Theta(\\log n)$ steps.\n\nTo adjoin an item\nfunction:\n\n```javascript\nadjoin_set_example_2\n\nadjoin_set(10, adjoin_set(15, adjoin_set(20, null)));\n\nhead(tail(head(tail(adjoin_set(10, adjoin_set(15, adjoin_set(20, null)))))));\n```\n\n```javascript\nadjoin_set2\n make_tree_function\n adjoin_set_example_2\n [ 10, [ null, [ null, null ] ] ]\n\nfunction adjoin_set(x, set) {\n return is_null(set)\n ? make_tree(x, null, null)\n : x === entry(set)\n ? set\n : x < entry(set)\n ? make_tree(entry(set),\n adjoin_set(x, left_branch(set)),\n right_branch(set))\n : // $\\texttt{x > entry(set)}$\n make_tree(entry(set),\n left_branch(set),\n adjoin_set(x, right_branch(set)));\n}\n```\n\nThe above claim that searching the tree can be performed in a logarithmic\nnumber of steps rests on the assumption that the tree is\nbalanced, i.e., that the\nleft and the right subtree of every tree have approximately the same\nnumber of elements, so that each subtree contains about half the\nelements of its parent.\n\nBut how can we be certain that the trees we\nconstruct will be balanced?\n\nEven if we start with a balanced tree,\nadding elements with\nadjoin_set\nmay produce an unbalanced result.", + "token_count": 276, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Representing Sets", + "chunk_index": 6, + "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_6" + }, + { + "content": "Even if we start with a balanced tree,\nadding elements with\nadjoin_set\nmay produce an unbalanced result.\n\nSince the position of a newly adjoined\nelement depends on how the element compares with the items already in the\nset, we can expect that if we add elements randomly the tree\nwill tend to be balanced on the average.\n\nBut this is not a guarantee.\n\nFor\nexample, if we start with an empty set and adjoin the numbers 1 through 7\nin sequence we end up with the highly unbalanced tree shown in\nfigure.\n\nIn this tree all the left\nsubtrees are empty, so it has no advantage over a simple ordered list.\n\nOne\nway to solve this problem is to define an operation that transforms an\narbitrary tree into a balanced tree with the same elements.\n\nThen we can perform this transformation after every few\nadjoin_set\noperations to keep our set in balance.\n\nThere are also other ways to solve\nthis problem, most of which involve designing new data structures for which\nsearching and insertion both can be done in\n$\\Theta(\\log n)$\nsteps.\n\nWe have examined options for using lists to represent sets and have\nseen how the choice of representation for a data object can have a\nlarge impact on the performance of the programs that use the data.\n\nAnother reason for concentrating on sets is that the techniques\ndiscussed here appear again and again in applications involving\ninformation retrieval.\n\nkey.\n\nA key can be anything that uniquely identifies the\nrecord.\n\nFor a personnel file, it might be an employee s ID number.\n\nFor an accounting system, it might be a transaction number.\n\nWhatever\nthe key is, when we define the record as a data structure we should\ninclude a\nfunction\nthat retrieves the key associated with a given record.", + "token_count": 300, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Representing Sets", + "chunk_index": 7, + "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_7" + }, + { + "content": "Whatever\nthe key is, when we define the record as a data structure we should\ninclude a\nfunction\nthat retrieves the key associated with a given record.\n\nNow we represent the data base as a set of records.\n\nTo locate the record\nwith a given key we use a\nfunction\nThe function lookup\nis implemented in almost the same way as\nis_element_of_set.\n\nFor example, if the set of records is implemented as an unordered list, we\ncould use\n\n```javascript\nrecord\n\nfunction make_record(key, data) {\n return pair(key, data);\n}\nfunction key(record) {\n return head(record);\n}\nfunction data(record) {\n return tail(record);\n}\n```\n\n```javascript\nrecord\n lookup_example\n [ 3, 'Earth' ]\n\nfunction lookup(given_key, set_of_records) {\n return is_null(set_of_records)\n ? false\n : equal(given_key, key(head(set_of_records)))\n ? head(set_of_records)\n : lookup(given_key, tail(set_of_records));\n}\n```\n\n```javascript\nlookup_example\n\nlookup(3, list(make_record(2, \"Venus\"),\n make_record(5, \"Jupiter\"),\n make_record(4, \"Mars\"),\n make_record(3, \"Earth\"),\n make_record(6, \"Saturn\")));\n```\n\nOf course, there are better ways to represent large sets than as unordered\nlists.\n\nInformation-retrieval systems in which records have to be\nrandomly accessed are typically implemented by a tree-based\nmethod, such as the binary-tree representation discussed previously.\n\nIn designing such a system the methodology of data abstraction\ncan be a great help.\n\nThe designer can create an initial implementation\nusing a simple, straightforward representation such as unordered lists.\n\nThis will be unsuitable for the eventual system, but it can be useful in\nproviding a quick and dirty data base with which to test the\nrest of the system.\n\nLater on, the data representation can be modified to\nbe more sophisticated.\n\nIf the data base is accessed in terms of abstract\nselectors and constructors, this change in representation will not require\nany changes to the rest of the system.", + "token_count": 279, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Representing Sets", + "chunk_index": 8, + "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_8" + }, + { + "content": "So far, we have used strings in order to display messages,\nusing the functions display and\nerror (as for example in\nexercise ).\n\nWe can form compound data using strings and have lists such as\n\n```javascript\nlist(\"a\", \"b\", \"c\", \"d\")\nlist(23, 45, 17)\nlist(list(\"Jakob\", 27), list(\"Lova\", 9), list(\"Luisa\", 24))\n```\n\nIn order to distinguish strings from names, we surround them z denotes the value of the name z , whereas the JavaScript expression \"z\" denotes\n\na string that consists of a single character, namely the last letter in the English alphabet in lower case.\n\nVia quotation marks, we can distinguish between strings and names:\n\n```javascript\nab\n\nconst a = 1;\nconst b = 2;\n```\n\n```javascript\nlist_ab\n ab\n\t [ 1, [ 2, null ] ]\n\nlist(a, b);\n```\n\n```javascript\nlist_quote_a_quote_b\n\t [ 'a', [ 'b', null ] ]\n\nlist(\"a\", \"b\");\n```\n\n```javascript\nlist_quote_a_b\n ab\n\t [ 'a', [ 2, null ] ]\n\nlist(\"a\", b);\n```\n\nIn section , we introduced\n=== and\n!==\nas primitive predicates on numbers.\n=== and\n!==.\n\nThe predicate\n===\nreturns true if and only\nif the two strings are the same, and\n!==\nreturns true if and only\nif the two strings are not the same. === , we can implement\na useful function called member.\n\nThis takes two arguments: a string and a list of strings or\na number and a list of numbers.\n\nIf the first argument is\nnot contained in the list (i.e., is not\n=== to any item in the list),\nthen member returns\nnull.\n\nOtherwise, it returns the\nsublist of the list beginning with the first occurrence of the\nstring or number:\n\n```javascript\nmemq\n memq_example\n\t null\n\nfunction member(item, x) {\n return is_null(x)\n ? null\n : item === head(x)\n ? x\n : member(item, tail(x));\n}\n```\n\nFor example, the value of\n\n```javascript\nmemq_example\n memq\n\nmember(\"apple\", list(\"pear\", \"banana\", \"prune\"))\n\nmember(\"apple\", list(\"pear\", \"banana\", \"prune\"));\n```", + "token_count": 310, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Strings", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Data_Strings_1" + }, + { + "content": "For example, the value of\n\nis null , whereas the value of\n\n```javascript\nmemq\n\t [ 'apple', [ 'pear', null ] ]\n\nmember(\"apple\", list(\"x\", \"y\", \"apple\", \"pear\"))\n\nmember(\"apple\", list(\"x\", \"y\", \"apple\", \"pear\"));\n```\n\nis list(\"apple\", \"pear\").", + "token_count": 35, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Strings", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Data_Strings_2" + }, + { + "content": "In the previous section, we saw how to design systems in which data\nobjects can be represented in more than one way.\n\nThe key idea is to\nlink the code that specifies the data operations to the several\nrepresentations by means of generic interface\nfunctions.\n\nNow we will see how to use this same idea not only to define operations\nthat are generic over different representations but also to define\noperations that are\n(add_rat,\nsub_rat,\nmul_rat,\ndiv_rat)\nof section , and the complex-number\narithmetic that we implemented in\nsection.\n\nWe will now use\ndata-directed techniques to construct a package of arithmetic operations\nthat incorporates all the arithmetic packages we have already constructed.\n\nFigure\nshows the structure of the system we\nshall build.\n\nNotice the\nnumbers, there is a single\nfunction\nThe function add\nis part of a generic interface that allows the separate ordinary-arithmetic,\nrational-arithmetic, and complex-arithmetic packages to be accessed\nuniformly by programs that use numbers.\n\nAny individual arithmetic package\n(such as the complex package) may itself be accessed through generic\nfunctions\n(such as\nadd_complex)\nthat combine packages designed for different representations (such as\nrectangular and polar).\n\nMoreover, the structure of the system is additive,\nso that one can design the individual arithmetic packages separately and\ncombine them to produce a generic arithmetic system.\n\nGeneric", + "token_count": 216, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Data_Systems_with_Generic_Operations_1" + }, + { + "content": "We have seen how to define a unified arithmetic system that\nencompasses ordinary numbers, complex numbers, rational numbers, and\nany other type of number we might decide to invent, but we have\nignored an important issue.\n\nThe operations we have defined so far\ntreat the different data types as being completely independent.\n\nThus,\nthere are separate packages for adding, say, two ordinary numbers, or\ntwo complex numbers.\n\nWhat we have not yet considered is the fact that\nit is meaningful to define operations that cross the type boundaries,\nsuch as the addition of a complex number to an ordinary number.\n\nWe\nhave gone to great pains to introduce barriers between parts of our\nprograms so that they can be developed and understood separately.\n\nWe\nwould like to introduce the cross-type operations in some carefully\ncontrolled way, so that we can support them\nwithout seriously violating our module boundaries.\n\nOne way to handle\nfunction\nfor each possible combination of types for which the operation is valid.\n\nFor example, we could extend the complex-number package so that it\nprovides a\nfunction\nfor adding complex numbers to ordinary numbers and installs this in the\ntable using the tag\nlist(\"complex\", \"javascript_number\") :\n\n```javascript\nadd_complex_to_javascript_number_example\n\nconst c = make_complex_from_real_imag(4, 3);\nconst n = make_javascript_number(7);\n\nadd(c, n);\n```\n\n```javascript\nadd_complex_to_javascript_number\n install_javascript_number_package_usage\n install_complex_package_usage\n add_complex_to_javascript_number_example\n [ 'complex', [ 'rectangular', [ 11, 3 ] ] ]\n\n// to be included in the complex package\nfunction add_complex_to_javascript_num(z, x) {\n return make_complex_from_real_imag(real_part(z) + x, imag_part(z));\n}\nput(\"add\", list(\"complex\", \"javascript_number\"),\n (z, x) => tag(add_complex_to_javascript_num(z, x)));\n```\n\nThis technique works, but it is cumbersome.\n\nWith such a system, the\ncost of introducing a new type is not just the construction of the\npackage of\nfunctions\nfor that type but also the construction and installation of the\nfunctions\nthat implement the cross-type operations.", + "token_count": 299, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Combining Data of Different Types", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_1" + }, + { + "content": "With such a system, the\ncost of introducing a new type is not just the construction of the\npackage of\nfunctions\nfor that type but also the construction and installation of the\nfunctions\nthat implement the cross-type operations.\n\nThis can easily be much more\ncode than is needed to define the operations on the type itself.\n\nThe\nmethod also undermines our ability to combine separate packages additively,\nor least to limit the extent to which the implementors of the individual\npackages need to take account of other packages.\n\nFor instance, in the\nexample above, it seems reasonable that handling mixed operations on\ncomplex numbers and ordinary numbers should be the responsibility of\nthe complex-number package.\n\nCombining rational numbers and complex\nnumbers, however, might be done by the complex package, by the rational\npackage, or by some third package that uses operations extracted from\nthese two packages.\n\nFormulating coherent policies on the division of\nresponsibility among packages can be an overwhelming task in designing\nsystems with many packages and many cross-type operations.\n\nIn the general situation of completely unrelated operations acting on\ncompletely unrelated types, implementing explicit cross-type operations,\ncumbersome though it may be, is the best that one can hope for.\n\nFortunately, we can usually do better by taking advantage of additional\nstructure that may be latent in our type system.\n\nOften the different\ndata types are not completely independent, and there may be ways by which\nobjects of one type may be viewed as being of another type.\n\nThis process\nis called coercion.\n\nFor example, if we are asked to\narithmetically combine an ordinary number with a complex number, we can\nview the ordinary number as a complex number whose imaginary part is zero.", + "token_count": 285, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Combining Data of Different Types", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_2" + }, + { + "content": "For example, if we are asked to\narithmetically combine an ordinary number with a complex number, we can\nview the ordinary number as a complex number whose imaginary part is zero.\n\nThis transforms the problem to that of combining two complex numbers, which\ncan be handled in the ordinary way by the complex-arithmetic package.\n\nIn general, we can implement this idea by designing\nfunctions\nthat transform an object of one type into an equivalent\nobject of another type.\n\nHere is a typical coercion\nfunction,\nwhich transforms a given ordinary number to a complex number with that real\npart and zero imaginary part:\n\n```javascript\njavascript_number_to_complex\n\nfunction javascript_number_to_complex(n) {\n return make_complex_from_real_imag(contents(n), 0);\n}\n```\n\nfunctions in a special coercion table, indexed under the names of the two types:\n\n```javascript\nput_get_coercion\n\nlet coercion_list = null;\n\nfunction clear_coercion_list() {\n coercion_list = null;\n}\n\nfunction put_coercion(type1, type2, item) {\n if (is_null(get_coercion(type1, type2))) {\n coercion_list = pair(list(type1, type2, item),\n coercion_list);\n } else {\n return coercion_list;\n }\n}\n\nfunction get_coercion(type1, type2) {\n function get_type1(list_item) {\n return head(list_item);\n }\n function get_type2(list_item) {\n return head(tail(list_item));\n }\n function get_item(list_item) {\n return head(tail(tail(list_item)));\n }\n function get_coercion_iter(items) {\n if (is_null(items)) {\n return undefined;\n } else {\n const top = head(items);\n return equal(type1, get_type1(top)) &&\n equal(type2, get_type2(top))\n ? get_item(top)\n : get_coercion_iter(tail(items));\n }\n }\n return get_coercion_iter(coercion_list);\n}\n```\n\n```javascript\nput_coercion_usage\n put_get_coercion\n javascript_number_to_complex\n install_complex_package_usage\n put_coercion_usage_example\n put_get_coercion\n\nput_coercion(\"javascript_number\", \"complex\",\n javascript_number_to_complex);\n```\n\n```javascript\nput_coercion_usage_example\n\nget_coercion(\"javascript_number\", \"complex\");\n```\n\n(We assume that there are\nput_coercion\nand\nget_coercion\nfunctions\navailable for manipulating this table.) Generally some of the slots in\nthe table will be empty, because it is not generally possible to coerce\nan arbitrary data object of each type into all other types.\n\nFor example,\nthere is no way to coerce an arbitrary complex number to an ordinary\nnumber, so there will be no general\ncomplex_to_javascript_number\nfunction\nincluded in the table.", + "token_count": 302, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Combining Data of Different Types", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_3" + }, + { + "content": "For example,\nthere is no way to coerce an arbitrary complex number to an ordinary\nnumber, so there will be no general\ncomplex_to_javascript_number\nfunction\nincluded in the table.\n\nOnce the coercion table has been set up, we can handle coercion in a\nuniform manner by modifying the\napply_generic\nfunction\nof section.\n\nWhen asked to apply an\noperation, we first check whether the operation is defined for the\narguments types, just as before.\n\nIf so, we dispatch to the\nfunction\nfound in the operation-and-type table.\n\nOtherwise, we try coercion.\n\nFor\nsimplicity, we consider only the case where there are two\narguments.\nfunction:", + "token_count": 101, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Combining Data of Different Types", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_4" + }, + { + "content": "For\nsimplicity, we consider only the case where there are two\narguments.\nfunction:\n\n```javascript\nbase_operation_table\n\n// operation_table, put and get\n// from chapter 3 (section 3.3.3)\nfunction assoc(key, records) {\n return is_null(records)\n ? undefined\n : equal(key, head(head(records)))\n ? head(records)\n : assoc(key, tail(records));\n}\nfunction make_table() {\n const local_table = list(\"*table*\");\n function lookup(key_1, key_2) {\n const subtable = assoc(key_1, tail(local_table));\n if (is_undefined(subtable)) {\n return undefined;\n } else {\n const record = assoc(key_2, tail(subtable));\n if (is_undefined(record)) {\n return undefined;\n } else {\n return tail(record);\n }\n }\n }\n function insert(key_1, key_2, value) {\n const subtable = assoc(key_1, tail(local_table));\n if (is_undefined(subtable)) {\n set_tail(local_table,\n pair(list(key_1, pair(key_2, value)),\n tail(local_table)));\n } else {\n const record = assoc(key_2, tail(subtable));\n if (is_undefined(record)) {\n set_tail(subtable,\n pair(pair(key_2, value),\n tail(subtable)));\n } else {\n set_tail(record, value);\n }\n }\n }\n function dispatch(m) {\n return m === \"lookup\"\n ? lookup\n : m === \"insert\"\n ? insert\n : \"undefined operation -- table\";\n }\n return dispatch;\n}\nconst operation_table = make_table();\nconst get = operation_table(\"lookup\");\nconst put = operation_table(\"insert\");\n\n// In Source, most functions have a fixed number of arguments.\n// (The function list is the only exception, to this so far.)\n// The function apply_in_underlying_javascript allows us to\n// apply any given function fun to all elements of the argument\n// list args, as if they were separate arguments\nfunction apply(fun, args) {\n return apply_in_underlying_javascript(fun, args);\n}\nfunction add(x, y) {\n return apply_generic(\"add\", list(x, y));\n}\nfunction sub(x, y) {\n return apply_generic(\"sub\", list(x, y));\n}\nfunction mul(x, y) {\n return apply_generic(\"mul\", list(x, y));\n}\nfunction div(x, y) {\n return apply_generic(\"div\", list(x, y));\n}\n\nfunction attach_tag(type_tag, contents) {\n return pair(type_tag, contents);\n}\nfunction type_tag(datum) {\n return is_pair(datum)\n ? head(datum)\n : error(datum, \"bad tagged datum -- type_tag\");\n}\nfunction contents(datum) {\n return is_pair(datum)\n ? tail(datum)\n : error(datum, \"bad tagged datum -- contents\");\n}\n```", + "token_count": 296, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Combining Data of Different Types", + "chunk_index": 5, + "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_5" + }, + { + "content": "For\nsimplicity, we consider only the case where there are two\narguments.\nfunction:\n\n```javascript\njavascript_number_package\n base_operation_table\n\nfunction install_javascript_number_package() {\n function tag(x) {\n return attach_tag(\"javascript_number\", x);\n }\n put(\"add\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x + y));\n put(\"sub\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x - y));\n put(\"mul\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x * y));\n put(\"div\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x / y));\n put(\"make\", \"javascript_number\",\n x => tag(x));\n return \"done\";\n}\ninstall_javascript_number_package();\n\nfunction make_javascript_number(n) {\n return get(\"make\", \"javascript_number\")(n);\n}\n```", + "token_count": 79, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Combining Data of Different Types", + "chunk_index": 6, + "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_6" + }, + { + "content": "For\nsimplicity, we consider only the case where there are two\narguments.\nfunction:\n\n```javascript\ncomplex_number_package\n base_operation_table\n\n// generic selector functions for complex numbers\n\nfunction real_part(z) {\n return apply_generic(\"real_part\", list(z));\n}\nfunction imag_part(z) {\n return apply_generic(\"imag_part\", list(z));\n}\nfunction magnitude(z) {\n return apply_generic(\"magnitude\", list(z));\n}\nfunction angle(z) {\n return apply_generic(\"angle\", list(z));\n}\nfunction square(x) {\n return x * x;\n}\n\nfunction install_rectangular_package() {\n function real_part(z) { return head(z); }\n function imag_part(z) { return tail(z); }\n function make_from_real_imag(x, y) { return pair(x, y); }\n function magnitude(z) {\n return math_sqrt(square(real_part(z)) +\n square(imag_part(z)));\n }\n function angle(z) {\n return math_atan2(imag_part(z), real_part(z));\n }\n function make_from_mag_ang(r, a) {\n return pair(r * math_cos(a), r * math_sin(a));\n }\n // interface to the rest of the system\n function tag(x) {\n return attach_tag(\"rectangular\", x);\n }\n put(\"real_part\", list(\"rectangular\"), real_part);\n put(\"imag_part\", list(\"rectangular\"), imag_part);\n put(\"magnitude\", list(\"rectangular\"), magnitude);\n put(\"angle\", list(\"rectangular\"), angle);\n put(\"make_from_real_imag\", \"rectangular\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"rectangular\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\ninstall_rectangular_package();\n\nfunction install_polar_package() {\n // internal functions\n function magnitude(z) { return head(z); }\n function angle(z) { return tail(z); }\n function make_from_mag_ang(r, a) { return pair(r, a); }\n function real_part(z) {\n return magnitude(z) * math_cos(angle(z));\n }\n function imag_part(z) {\n return magnitude(z) * math_sin(angle(z));\n }\n function make_from_real_imag(x, y) {\n return pair(math_sqrt(square(x) + square(y)),\n math_atan2(y, x));\n }\n\n // interface to the rest of the system\n function tag(x) { return attach_tag(\"polar\", x); }\n put(\"real_part\", list(\"polar\"), real_part);\n put(\"imag_part\", list(\"polar\"), imag_part);\n put(\"magnitude\", list(\"polar\"), magnitude);\n put(\"angle\", list(\"polar\"), angle);\n put(\"make_from_real_imag\", \"polar\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"polar\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\ninstall_polar_package();\n\nfunction install_complex_package() {\n // imported functions from rectangular and polar packages\n function make_from_real_imag(x, y) {\n return get(\"make_from_real_imag\", \"rectangular\")(x, y);\n }\n function make_from_mag_ang(r, a) {\n return get(\"make_from_mag_ang\", \"polar\")(r, a);\n }\n\n // internal functions\n function add_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) +\n real_part(z2),\n imag_part(z1) +\n imag_part(z2));\n }\n function sub_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) -\n real_part(z2),\n imag_part(z1) -\n imag_part(z2));\n }\n function mul_complex(z1, z2) {\n return make_from_mag_ang(magnitude(z1) *\n magnitude(z2),\n angle(z1) +\n angle(z2));\n }\n function div_complex(z1, z2) {\n return make_from_mag_ang(magnitude(z1) /\n magnitude(z2),\n angle(z1) -\n angle(z2));\n }\n\n // interface to rest of the system\n function tag(z) {\n return attach_tag(\"complex\", z);\n }\n put(\"add\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(add_complex(z1, z2)));\n put(\"sub\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(sub_complex(z1, z2)));\n put(\"mul\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(mul_complex(z1, z2)));\n put(\"div\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(div_complex(z1, z2)));\n put(\"make_from_real_imag\", \"complex\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"complex\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\ninstall_complex_package();\n\nfunction make_complex_from_real_imag(x, y){\n return get(\"make_from_real_imag\", \"complex\")(x, y);\n}\nfunction make_complex_from_mag_ang(r, a){\n return get(\"make_from_mag_ang\", \"complex\")(r, a);\n}\n```", + "token_count": 413, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Combining Data of Different Types", + "chunk_index": 7, + "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_7" + }, + { + "content": "For\nsimplicity, we consider only the case where there are two\narguments.\nfunction:\n\n```javascript\ncoercion_support\n\n// coercion support\n\nlet coercion_list = null;\n\nfunction clear_coercion_list() {\n coercion_list = null;\n}\n\nfunction put_coercion(type1, type2, item) {\n if (is_undefined(get_coercion(type1, type2))) {\n coercion_list = pair(list(type1, type2, item),\n coercion_list);\n } else {\n return coercion_list;\n }\n}\n\nfunction get_coercion(type1, type2) {\n function get_type1(list_item) {\n return head(list_item);\n }\n function get_type2(list_item) {\n return head(tail(list_item));\n }\n function get_item(list_item) {\n return head(tail(tail(list_item)));\n }\n function get_coercion_iter(items) {\n if (is_null(items)) {\n return undefined;\n } else {\n const top = head(items);\n return equal(type1, get_type1(top)) &&\n equal(type2, get_type2(top))\n ? get_item(top)\n : get_coercion_iter(tail(items));\n }\n }\n return get_coercion_iter(coercion_list);\n}\n```\n\n```javascript\napply_generic_with_coercion_example\n base_operation_table\n javascript_number_package\n complex_number_package\n coercion_support\n\nfunction javascript_number_to_complex(n) {\n return make_complex_from_real_imag(contents(n), 0);\n}\n\nput_coercion(\"javascript_number\", \"complex\",\n javascript_number_to_complex);\n\nconst c = make_complex_from_real_imag(4, 3);\nconst n = make_javascript_number(7);\n\nadd(c, n);\n```\n\n```javascript\napply_generic_with_coercion\n apply_generic_with_coercion_example\n [ 'complex', [ 'rectangular', [ 11, 3 ] ] ]\n\nfunction apply_generic(op, args) {\n const type_tags = map(type_tag, args);\n const fun = get(op, type_tags);\n if (! is_undefined(fun)) {\n return apply(fun, map(contents, args));\n } else {\n if (length(args) === 2) {\n const type1 = head(type_tags);\n const type2 = head(tail(type_tags));\n const a1 = head(args);\n const a2 = head(tail(args));\n const t1_to_t2 = get_coercion(type1, type2);\n const t2_to_t1 = get_coercion(type2, type1);\n return ! is_undefined(t1_to_t2)\n ? apply_generic(op, list(t1_to_t2(a1), a2))\n : ! is_undefined(t2_to_t1)\n ? apply_generic(op, list(a1, t2_to_t1(a2)))\n : error(list(op, type_tags),\n \"no method for these types\");\n } else {\n return error(list(op, type_tags),\n \"no method for these types\");\n }\n }\n}\n```\n\nThis coercion has many advantages over the method of defining\nexplicit cross-type operations, as outlined above.\n\nAlthough we still\nneed to write coercion\nfunctions\nto relate the types (possibly $n^2$\nfunctions\nfor a system with $n$ types), we need to write\nonly one\nfunction\nfor each pair of types rather than a different\nfunction\nfor each collection of types and each generic operation.", + "token_count": 301, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Combining Data of Different Types", + "chunk_index": 8, + "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_8" + }, + { + "content": "Although we still\nneed to write coercion\nfunctions\nto relate the types (possibly $n^2$\nfunctions\nfor a system with $n$ types), we need to write\nonly one\nfunction\nfor each pair of types rather than a different\nfunction\nfor each collection of types and each generic operation.\n\nOn the other hand, there may be applications for which our coercion is not general enough.\n\nEven when neither of the objects to be\ncombined can be converted to the type of the other it may still be\npossible to perform the operation by converting both objects to a\nthird type.\n\nIn order to deal with such complexity and still preserve\nmodularity in our programs, it is usually necessary to build systems\nthat take advantage of still further structure in the relations among\ntypes, as we discuss next.\n\nThe coercion presented above relied on the existence of natural\nrelations between pairs of types.\n\nOften there is more global\nstructure in how the different types relate to each other.\n\nFor\ninstance, suppose we are building a generic arithmetic system to\nhandle integers, rational numbers, real numbers, and complex numbers.\n\nIn such a system, it is quite natural to regard an integer as a\nspecial kind of rational number, which is in turn a special kind of\nreal number, which is in turn a special kind of complex number.\n\nWhat\nwe actually have is a so-called hierarchy of types , in which,\nfor example, integers are a\nsubtype of rational numbers (i.e.,\nany operation that can be applied to a rational number can\nautomatically be applied to an integer).\n\nConversely, we say that\nrational numbers form a\nsupertype of integers.", + "token_count": 275, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Combining Data of Different Types", + "chunk_index": 9, + "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_9" + }, + { + "content": "Conversely, we say that\nrational numbers form a\nsupertype of integers.\n\nThe particular\nhierarchy we have here is of a very simple kind, in which each type\nhas at most one supertype and at most one subtype.\n\nSuch a structure,\ncalled a tower , is illustrated in\nfigure.\n\nA tower of types.\n\nIf we have a tower structure, then we can greatly simplify the problem\nof adding a new type to the hierarchy, for we need only specify how\nthe new type is embedded in the next supertype above it and how it is\nthe supertype of the type below it.\n\nFor example, if we want to add an\ninteger to a complex number, we need not explicitly define a special\ncoercion\nfunction\ninteger_to_complex.\n\nInstead, we define how an integer can be transformed into a rational\nnumber, how a rational number is transformed into a real number, and how\na real number is transformed into a complex number.\n\nWe then allow the\nsystem to transform the integer into a complex number through these steps\nand then add the two complex numbers.\n\napply_generic\nfunction\nin the following way: For each type, we need to supply a\nfunction,\nwhich raises objects of that type one level in the tower.\n\nThen when the system is required to operate on objects of different types\nit can successively raise the lower types until all the objects are at\nthe same level in the tower.\n\n(Exercises\nand\nconcern the details of implementing such a strategy.)\n\nAnother advantage of a tower is that we can easily implement the notion\nthat every type inherits all operations defined on a\nsupertype.", + "token_count": 272, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Combining Data of Different Types", + "chunk_index": 10, + "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_10" + }, + { + "content": "Another advantage of a tower is that we can easily implement the notion\nthat every type inherits all operations defined on a\nsupertype.\n\nFor instance, if we do not supply a special\nfunction\nfor finding the real part of an integer, we should nevertheless expect\nthat\nreal_part\nwill be defined for integers by virtue of the fact that integers are a\nsubtype of complex numbers.\n\nIn a tower, we can arrange for this to happen\nin a uniform way by modifying\napply_generic.\n\nIf the required operation is not directly defined for the type of the\nobject given, we raise the object to its supertype and try again.\n\nWe thus\ncrawl up the tower, transforming our argument as we go, until we either\nfind a level at which the desired operation can be performed or hit the\ntop (in which case we give up).\n\nlower a data object to the\nsimplest representation.\n\nFor example, if we add\n$2+3i$ to $4-3i$ ,\nit would be nice to obtain the answer as the integer 6 rather than as the\ncomplex number $6+0i$.\n\nExercise discusses a way to implement\nsuch a lowering operation.\n\n(The trick is that we need a general way\nto distinguish those objects that can be lowered, such as\n$6+0i$ , from those that cannot, such as\n$6+2i$.)\n\nIf the data types in our system can be naturally arranged in a tower,\nthis greatly simplifies the problems of dealing with generic operations\non different types, as we have seen.\n\nUnfortunately, this is usually\nnot the case.\n\nFigure\nillustrates a more complex arrangement of mixed types, this one showing\nrelations among different types of geometric figures.\n\nWe see that, in\ngeneral,\nraise a type\nin the hierarchy.", + "token_count": 285, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Combining Data of Different Types", + "chunk_index": 11, + "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_11" + }, + { + "content": "We see that, in\ngeneral,\nraise a type\nin the hierarchy.\n\nFinding the correct supertype in which\nto apply an operation to an object may involve considerable searching\nthrough the entire type network on the part of a\nfunction\nsuch as\napply_generic.\n\nSince there generally are multiple subtypes for a type, there is a similar\nproblem in coercing a value down the type hierarchy.\n\nDealing with large numbers of interrelated types while still preserving\nmodularity in the design of large systems is very difficult, and is an area\nof much current research.", + "token_count": 92, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Combining Data of Different Types", + "chunk_index": 12, + "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_12" + }, + { + "content": "The manipulation of symbolic algebraic expressions is a complex\nprocess that illustrates many of the hardest problems that occur in\nthe design of large-scale systems.\n\nAn\ntypes, which are\noften useful for directing the processing of expressions.\n\nFor example, we\ncould describe the expression\n\\[ x^{2}\\, \\sin (y^2+1)+x\\, \\cos 2y+\\cos (y^3 -2y^2) \\]\nas a polynomial in $x$ with coefficients that\nare trigonometric functions of polynomials in\n$y$ whose coefficients are integers.\n\nWe will not attempt to develop a complete algebraic-manipulation\nsystem here.\n\nSuch systems are exceedingly complex programs, embodying\ndeep algebraic knowledge and elegant algorithms.\n\nWhat we will do is\nlook at a simple but important part of algebraic manipulation: the\narithmetic of polynomials.\n\nWe will illustrate the kinds of decisions\nthe designer of such a system faces, and how to apply the ideas of\nabstract data and generic operations to help organize this effort.\n\nOur first task in designing a system for performing arithmetic on\npolynomials is to decide just what a polynomial is.\n\nPolynomials are\nnormally defined relative to certain variables (the\nindeterminates of the polynomial).\n\nFor simplicity, we will\nrestrict ourselves to polynomials having just one indeterminate\n(univariate polynomials ).\n\\[ 5x^2 +3x +7 \\]\nis a simple polynomial in $x$ , and\n\\[ (y^2 +1)x^3 +(2y)x+1 \\]\nis a polynomial in $x$ whose coefficients are\npolynomials in $y$.\n\nAlready we are skirting some thorny issues.\n\nIs the first of these\npolynomials the same as the polynomial\n$5y^2 +3y +7$ , or not?\n\nA reasonable answer\nmight be yes, if we are considering a polynomial purely as a\nmathematical function, but no, if we are considering a polynomial to be a\nsyntactic form.\n\nThe second polynomial is algebraically equivalent\nto a polynomial in $y$ whose coefficients are\npolynomials in $x$.", + "token_count": 294, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Example: Symbolic Algebra", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_1" + }, + { + "content": "The second polynomial is algebraically equivalent\nto a polynomial in $y$ whose coefficients are\npolynomials in $x$.\n\nShould our system recognize\nthis, or not?\n\nFurthermore, there are other ways to represent a\npolynomial for example, as a product of factors, or (for a\nunivariate polynomial) as the set of roots, or as a listing of the values\nof the polynomial at a specified set of points. polynomial will be a\nparticular syntactic form, not its underlying mathematical meaning.\n\nNow we must consider how to go about doing arithmetic on polynomials.\n\nIn this simple system, we will consider only addition and\nmultiplication.\n\nMoreover, we will insist that two polynomials to be\ncombined must have the same indeterminate.\n\nWe will approach the design of our system by following the familiar\ndiscipline of data abstraction.\n\nWe will represent polynomials using a\ndata structure called a\npoly , which consists of a variable and a\nterm_list\nthat extract those parts from a poly and a constructor\nmake_poly\nthat assembles a poly from a given variable and a term list.\n\nA variable will be just a\nstring,\nso we can use the\nis_same_variable\nfunction\nof section to compare\nvariables.\n\nThe following\nfunctions\ndefine\n\n```javascript\nadd_mul_poly\n install_javascript_number_package_usage\n make_polynomial_requires\n make_polynomial\n make_polynomial_example\n\nfunction add_poly(p1, p2) {\n return is_same_variable(variable(p1), variable(p2))\n ? make_poly(variable(p1),\n add_terms(term_list(p1), term_list(p2)))\n : error(list(p1, p2), \"polys not in same var -- add_poly\");\n}\nfunction mul_poly(p1, p2) {\n return is_same_variable(variable(p1), variable(p2))\n ? make_poly(variable(p1),\n mul_terms(term_list(p1), term_list(p2)))\n : error(list(p1, p2), \"polys not in same var -- mul_poly\");\n}\n```\n\nTo incorporate polynomials into our generic arithmetic system, we need\nto supply them with type tags.\n\nWe ll use the tag\n\"polynomial\",\nand install appropriate operations on tagged polynomials in the operation\ntable.", + "token_count": 282, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Example: Symbolic Algebra", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_2" + }, + { + "content": "We ll use the tag\n\"polynomial\",\nand install appropriate operations on tagged polynomials in the operation\ntable.\n\n```javascript\nWell embed all our code in an installation function\n\tfor the polynomial package,\n\tsimilar to the installation functions in\n\tsection:\n\n\t install_polynomial_package_template\n\t install_javascript_number_package_usage\n\t make_polynomial_requires\n\t make_polynomial\n\t make_polynomial_example\n\nfunction install_polynomial_package() {\n // internal functions\n // representation of poly\n function make_poly(variable, term_list) {\n return pair(variable, term_list);\n }\n function variable(p) { return head(p); }\n function term_list(p) { return tail(p); }\n functions is_same_variable and is_variable from section 2.3.2\n\n // representation of terms and term lists\n functions adjoin_term...coeff from text below\n\n function add_poly(p1, p2) { ... }\n functions used by add_poly\n function mul_poly(p1, p2) { ... }\n functions used by mul_poly\n\n // interface to rest of the system\n function tag(p) { return attach_tag(\"polynomial\", p); }\n put(\"add\", list(\"polynomial\", \"polynomial\"),\n (p1, p2) => tag(add_poly(p1, p2)));\n put(\"mul\", list(\"polynomial\", \"polynomial\"),\n (p1, p2) => tag(mul_poly(p1, p2)));\n put(\"make\", \"polynomial\",\n (variable, terms) => tag(make_poly(variable, terms)));\n return \"done\";\n}\n```\n\nPolynomial addition is performed termwise.\n\nTerms of the same order\n(i.e., with the same power of the indeterminate) must be combined.\n\nThis is done by forming a new term of the same order whose coefficient\nis the sum of the coefficients of the addends.\n\nTerms in one addend\nfor which there are no terms of the same order in the other addend are\nsimply accumulated into the sum polynomial being constructed.\n\nIn order to manipulate term lists, we will assume that we have a\nconstructor\nthe_empty_termlist\nthat returns an empty term list and a constructor\nadjoin_@term\nthat adjoins a new term to a term list.\n\nWe will also assume that we have\na predicate\nis_empty_termlist\nthat tells if a given term list is empty, a selector\nfirst_term\nthat extracts the highest-order term from a term list, and a selector\nrest_terms\nthat returns all but the highest-order term.", + "token_count": 300, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Example: Symbolic Algebra", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_3" + }, + { + "content": "We will also assume that we have\na predicate\nis_empty_termlist\nthat tells if a given term list is empty, a selector\nfirst_term\nthat extracts the highest-order term from a term list, and a selector\nrest_terms\nthat returns all but the highest-order term.\n\nTo manipulate terms,\nwe will suppose that we have a constructor\nmake_term\nthat constructs a term with given order and coefficient, and selectors\n\nHere is the function that constructs the term list for the sum of two\n\n```javascript\npolynomials;\n```\n\n```javascript\nnote that we slightly extend the syntax of\n\t by admitting another conditional\n statement in place of the block following\n else:\n```\n\n```javascript\nadd_terms\n install_javascript_number_package_usage\n make_polynomial_requires\n make_polynomial\n make_polynomial_example\n\nfunction add_terms(L1, L2) {\n if (is_empty_termlist(L1)) {\n return L2;\n } else if (is_empty_termlist(L2)) {\n return L1;\n } else {\n const t1 = first_term(L1);\n const t2 = first_term(L2);\n return order(t1) > order(t2)\n ? adjoin_term(t1, add_terms(rest_terms(L1), L2))\n : order(t1) < order(t2)\n ? adjoin_term(t2, add_terms(L1, rest_terms(L2)))\n : adjoin_term(make_term(order(t1),\n add(coeff(t1), coeff(t2))),\n add_terms(rest_terms(L1),\n rest_terms(L2)));\n }\n}\n```\n\nThe most important point to note here is that we used the generic addition function\n\nIn order to multiply two term lists, we multiply each term of the first\nlist by all the terms of the other list, repeatedly using\nmul_term_by_all_terms,\nwhich multiplies a given term by all terms in a given term list.\n\nThe\nresulting term lists (one for each term of the first list) are accumulated\ninto a sum.\n\nMultiplying two terms forms a term whose order is the sum of\nthe orders of the factors and whose coefficient is the product of the\ncoefficients of the factors:", + "token_count": 262, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Example: Symbolic Algebra", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_4" + }, + { + "content": "Multiplying two terms forms a term whose order is the sum of\nthe orders of the factors and whose coefficient is the product of the\ncoefficients of the factors:\n\n```javascript\nmul_terms\n install_javascript_number_package_usage\n make_polynomial_requires\n make_polynomial\n make_polynomial_example\n\nfunction mul_terms(L1, L2) {\n return is_empty_termlist(L1)\n ? the_empty_termlist\n : add_terms(mul_term_by_all_terms(\n first_term(L1), L2),\n mul_terms(rest_terms(L1), L2));\n}\nfunction mul_term_by_all_terms(t1, L) {\n if (is_empty_termlist(L)) {\n return the_empty_termlist;\n } else {\n const t2 = first_term(L);\n return adjoin_term(\n make_term(order(t1) + order(t2),\n mul(coeff(t1), coeff(t2))),\n mul_term_by_all_terms(t1, rest_terms(L)));\n }\n}\n```\n\nThis is really all there is to polynomial addition and multiplication.\n\nNotice that, since we operate on terms using the generic\nfunctions\n,\nthen we also are automatically able to handle operations on\npolynomials of different coefficient types, such as\n\\[\n\\begin{array}{l}\n{\\left[3x^2 +(2+3i)x+7\\right] \\cdot \\left[x^4 +\\frac{2}{3}x^2\n+(5+3i)\\right]}\n\\end{array}\n\\]\n\nBecause we installed the polynomial addition and multiplication\nfunctions\nadd_@poly\nand\nmul_poly\nin the generic arithmetic system as the\n\\[\n\\begin{array}{l}\n{\\left[ (y+1)x^2 +(y^2 +1)x+(y-1)\\right]\\cdot \\left[(y-2)x+(y^3 +7)\\right]}\n\\end{array}\n\\]\nThe reason is that when the system tries to combine coefficients, it\nwill dispatch through $y$ ), these will be combined\nusing\nadd_poly\nand\nmul_poly.\n\nThe result is a kind of\ndata-directed recursion in which, for example, a call to\nmul_poly\nwill result in recursive calls to\nmul_poly\nin order to multiply the coefficients.\n\nIf the coefficients of the\ncoefficients were themselves polynomials (as might be used to represent\npolynomials in three variables), the data direction would ensure that the\nsystem would follow through another level of recursive calls, and so on\nthrough as many levels as the structure of the data dictates.\n\nFinally, we must confront the job of implementing a good\nrepresentation for term lists.\n\nA term list is, in effect, a set of\ncoefficients keyed by the order of the term.", + "token_count": 291, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Example: Symbolic Algebra", + "chunk_index": 5, + "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_5" + }, + { + "content": "A term list is, in effect, a set of\ncoefficients keyed by the order of the term.\n\nHence, any of the\nmethods for representing sets, as discussed in\nsection , can be applied to this\ntask.\n\nOn the other hand, our\nfunctions\nadd_terms\nand\nmul_terms\nalways access term lists sequentially from highest to lowest order.\n\nThus, we will use some kind of ordered list representation.\n\nHow should we structure the list that represents a term list?\n\nOne\nconsideration is the density of the polynomials we intend\nto manipulate.\n\nA polynomial is said to be\ndense if it has nonzero coefficients in terms of most orders.\n\nIf it has many zero terms it is said to be\nsparse.\n\nFor example,\n\\[ A:\\quad x^5 +2x^4 +3x^2 -2x -5 \\]\nis a dense polynomial, whereas\n\\[ B:\\quad x^{100} +2x^2 +1 \\]\nis sparse.\n\n```javascript\nThe term list of a dense polynomial is most efficiently represented\n\tas a list of the coefficients.\n```\n\nFor example,\nthe polynomial\n$A$ above would be nicely represented as\nlist(1, 2, 0, 3, -2, -5).\n\nThe order of a term in this representation is the length of the sublist\nbeginning with that term s coefficient, decremented by 1. $B$ : There would be a giant list of zeros\npunctuated by a few lonely nonzero terms.\n\nA more reasonable representation\nof the term list of a sparse polynomial is as a list of the nonzero terms,\nwhere each term is a list containing the order of the term and the\ncoefficient for that order.\n\nIn such a , polynomial\n$B$ is efficiently represented as\nlist(list(100, 1), list(2, 2), list(0, 1)).\n\nAs most polynomial manipulations are performed on sparse polynomials, we\nwill use this method.\n\nWe will assume that term lists are represented as\nlists of terms, arranged from highest-order to lowest-order term.", + "token_count": 302, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Example: Symbolic Algebra", + "chunk_index": 6, + "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_6" + }, + { + "content": "We will assume that term lists are represented as\nlists of terms, arranged from highest-order to lowest-order term.\n\nOnce we\nhave made this decision, implementing the selectors and constructors for\nterms and term lists is straightforward:\n\n```javascript\nadjoin_term\n install_javascript_number_package_usage\n make_polynomial_requires\n make_polynomial\n make_polynomial_example\n\nfunction adjoin_term(term, term_list) {\n return is_equal_to_zero(coeff(term))\n ? term_list\n : pair(term, term_list);\n}\n\nconst the_empty_termlist = null;\n\nfunction first_term(term_list) { return head(term_list); }\n\nfunction rest_terms(term_list) { return tail(term_list); }\n\nfunction is_empty_termlist(term_list) { return is_null(term_list); }\n\nfunction make_term(order, coeff) { return list(order, coeff); }\n\nfunction order(term) { return head(term); }\n\nfunction coeff(term) { return head(tail(term)); }\n```\n\nwhere\nis_equal_to_zero\nis as defined in exercise.\n\n(See also\nexercise below.)\n\nUsers of the polynomial package will create (tagged) polynomials by means of the function:", + "token_count": 122, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Example: Symbolic Algebra", + "chunk_index": 7, + "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_7" + }, + { + "content": "Users of the polynomial package will create (tagged) polynomials by means of the function:\n\n```javascript\nmake_polynomial_requires\n is_same_variable\n\nfunction install_javascript_number_is_equal_to_zero() {\n put(\"is_equal_to_zero\", list(\"javascript_number\"),\n x => x === 0);\n return \"done\";\n}\ninstall_javascript_number_is_equal_to_zero();\n\nfunction is_equal_to_zero(x) {\n return apply_generic(\"is_equal_to_zero\", list(x));\n}\n\nfunction install_polynomial_package() {\n\n // internal functions\n\n // representation of poly\n function make_poly(variable, term_list) {\n return pair(variable, term_list);\n }\n function variable(p) { return head(p); }\n function term_list(p) { return tail(p); }\n\n // representation of terms and term lists\n function adjoin_term(term, term_list) {\n return is_equal_to_zero(coeff(term))\n ? term_list\n : pair(term, term_list);\n }\n const the_empty_termlist = null;\n function first_term(term_list) {\n return head(term_list);\n }\n function rest_terms(term_list) {\n return tail(term_list);\n }\n function is_empty_termlist(term_list) {\n return is_null(term_list);\n }\n function make_term(order, coeff) {\n return list(order, coeff);\n }\n function order(term) {\n return head(term);\n }\n function coeff(term) {\n return head(tail(term));\n }\n\n function add_poly(p1, p2) {\n return is_same_variable(variable(p1), variable(p2))\n ? make_poly(variable(p1),\n add_terms(term_list(p1),\n term_list(p2)))\n : error(list(p1, p2),\n \"polys not in same var -- add_poly\");\n }\n\n function add_terms(L1, L2) {\n if (is_empty_termlist(L1)) {\n return L2;\n }\n else if (is_empty_termlist(L2)) {\n return L1;\n }\n else {\n const t1 = first_term(L1);\n const t2 = first_term(L2);\n return order(t1) > order(t2)\n ? adjoin_term(t1, add_terms(rest_terms(L1), L2))\n : order(t1) < order(t2)\n ? adjoin_term(t2, add_terms(L1, rest_terms(L2)))\n : adjoin_term(make_term(order(t1),\n add(coeff(t1),\n coeff(t2))),\n add_terms(rest_terms(L1),\n rest_terms(L2)));\n }\n }\n\n function mul_poly(p1, p2) {\n return is_same_variable(variable(p1), variable(p2))\n ? make_poly(variable(p1),\n mul_terms(term_list(p1),\n term_list(p2)))\n : error(list(p1, p2),\n \"polys not in same var -- mul_poly\");\n }\n\n function mul_terms(L1, L2) {\n return is_empty_termlist(L1)\n ? the_empty_termlist\n : add_terms(mul_term_by_all_terms(\n first_term(L1), L2),\n mul_terms(rest_terms(L1), L2));\n }\n function mul_term_by_all_terms(t1, L) {\n if (is_empty_termlist(L)) {\n return the_empty_termlist;\n } else {\n const t2 = first_term(L);\n return adjoin_term(\n make_term(order(t1) + order(t2),\n mul(coeff(t1), coeff(t2))),\n mul_term_by_all_terms(t1, rest_terms(L)));\n }\n }\n\n // interface to rest of the system\n function tag(p) {\n return attach_tag(\"polynomial\", p);\n }\n put(\"add\", list(\"polynomial\", \"polynomial\"),\n (p1, p2) => tag(add_poly(p1, p2)));\n put(\"mul\", list(\"polynomial\", \"polynomial\"),\n (p1, p2) => tag(mul_poly(p1, p2)));\n put(\"make\", \"polynomial\",\n (variable, terms) =>\n tag(make_poly(variable, terms)));\n return \"done\";\n}\ninstall_polynomial_package();\n```", + "token_count": 308, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Example: Symbolic Algebra", + "chunk_index": 8, + "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_8" + }, + { + "content": "Users of the polynomial package will create (tagged) polynomials by means of the function:\n\n```javascript\nmake_polynomial\n install_javascript_number_package_usage\n make_polynomial_requires\n make_polynomial_example\n [ 3, [ [ 'javascript_number', 23 ], null ] ]\n\nfunction make_polynomial(variable, terms) {\n return get(\"make\", \"polynomial\")(variable, terms);\n}\n```\n\n```javascript\nmake_polynomial_example\n\nconst p1 = make_polynomial(\"x\",\n list(make_term(2, make_javascript_number(4)),\n make_term(1, make_javascript_number(3)),\n make_term(0, make_javascript_number(7))));\nconst p2 = make_polynomial(\"x\",\n list(make_term(2, make_javascript_number(5)),\n make_term(1, make_javascript_number(2)),\n make_term(0, make_javascript_number(10))));\n\nmul(p1, p2);\n\nconst p1 = make_polynomial(\"x\",\n list(list(2, make_javascript_number(4)),\n list(1, make_javascript_number(3)),\n list(0, make_javascript_number(7))));\nconst p2 = make_polynomial(\"x\",\n list(list(2, make_javascript_number(5)),\n list(1, make_javascript_number(2)),\n list(0, make_javascript_number(10))));\n\nhead(tail(tail(tail(mul(p1, p2)))));\n```\n\nOur polynomial system illustrates how objects of one type\n(polynomials) may in fact be complex objects that have objects of many\ndifferent types as parts.\n\nThis poses no real difficulty in defining\ngeneric operations.\n\nWe need only install appropriate generic operations\nfor performing the necessary manipulations of the parts of the\ncompound types.\n\nIn fact, we saw that polynomials form a kind of\nrecursive data abstraction, in that parts of a polynomial may\nthemselves be polynomials.\n\nOur generic operations and our\ndata-directed programming style can handle this complication without\nmuch trouble.\n\nOn the other hand, polynomial algebra is a system for which the data\ntypes cannot be naturally arranged in a tower.\n\nFor instance, it is\npossible to have polynomials in $x$ whose\ncoefficients are polynomials in $y$.\n\nIt is also\npossible to have polynomials in $y$ whose\ncoefficients are polynomials in $x$.\n\nNeither of\nthese types is above the other in any natural way, yet it is\noften necessary to add together elements from each set.\n\nThere are several\nways to do this.\n\nOne possibility is to convert one polynomial to the type\nof the other by expanding and rearranging terms so that both polynomials\nhave the same principal variable.", + "token_count": 287, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Example: Symbolic Algebra", + "chunk_index": 9, + "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_9" + }, + { + "content": "One possibility is to convert one polynomial to the type\nof the other by expanding and rearranging terms so that both polynomials\nhave the same principal variable.\n\nOne can impose a towerlike structure on\nthis by ordering the variables and thus always converting any polynomial\nto a\ncanonical form with the highest-priority variable\ndominant and the lower-priority variables buried in the coefficients.\n\nThis strategy works fairly well, except that the conversion may expand\na polynomial unnecessarily, making it hard to read and perhaps less\nefficient to work with.\n\nThe tower strategy is certainly not natural\nfor this domain or for any domain where the user can invent new types\ndynamically using old types in various combining forms, such as\ntrigonometric functions, power series, and integrals.\n\nIt should not be surprising that controlling\n\nWe can extend our generic arithmetic system to include rational\nfunctions.\n\nThese are fractions whose numerator and\ndenominator are polynomials, such as\n\\[\n\\begin{array}{l}\n\\dfrac{x+1}{x^3 -1}\n\\end{array}\n\\]\nThe system should be able to add, subtract, multiply, and divide\nrational functions, and to perform such computations as\n\\[\n\\begin{array}{lll}\n\\dfrac{x+1}{x^3 -1}+\\dfrac{x}{x^2 -1} & = & \\dfrac{x^3 +2x^2 +3x +1}{x^4 +\nx^3 -x-1}\n\\end{array}\n\\]\n(Here the sum has been simplified by removing common factors.\n\nOrdinary cross multiplication would have produced a\nfourth-degree polynomial over a fifth-degree polynomial.)\n\nIf we modify our rational-arithmetic package so that it uses generic operations, then it will do what we want, except for the problem of reducing\n\nfractions to lowest terms.\n\nWe can reduce polynomial fractions to lowest terms using the same idea\nwe used with integers: modifying\nmake_rat\nto divide both the numerator and the denominator by their greatest common\ndivisor.\n\nThe notion of\ngreatest common divisor makes sense for polynomials.", + "token_count": 289, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Example: Symbolic Algebra", + "chunk_index": 10, + "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_10" + }, + { + "content": "The notion of\ngreatest common divisor makes sense for polynomials.\n\nIn\nfact, we can compute the GCD of two polynomials using essentially the\nsame Euclid s Algorithm that works for integers.\n\n```javascript\nfunction gcd(a, b) {\n return b === 0\n ? a\n : gcd(b, a % b);\n}\n```\n\nUsing this, we could make the obvious modification to define a GCD operation that works on term lists:\n\n```javascript\nfunction gcd_terms(a, b) {\n return is_empty_termlist(b)\n ? a\n : gcd_terms(b, remainder_terms(a, b));\n}\n```\n\nwhere remainder_terms picks out the remainder component of the list returned by the term-list division operation div_terms that was implemented in exercise.\n\nWe can solve the problem exhibited in\nexercise if\nwe use the following modification of the GCD algorithm (which really\nworks only in the case of polynomials with integer coefficients).\n\nBefore performing any polynomial division in the GCD computation, we\nmultiply the dividend by an integer constant factor, chosen to\nguarantee that no fractions will arise during the division process.\n\nOur answer will thus differ from the actual GCD by an integer constant\nfactor, but this does not matter in the case of reducing rational\nfunctions to lowest terms; the GCD will be used to divide both the\nnumerator and denominator, so the integer constant factor will cancel\nout.\n\nMore precisely, if $P$ and\n$Q$ are polynomials, let\n$O_1$ be the order of\n$P$ (i.e., the order of the largest term of\n$P$ ) and let $O_2$\nbe the order of $Q$.\n\nLet\n$c$ be the leading coefficient of\n$Q$.\n\nThen it can be shown that, if we multiply\n$P$ by the\nintegerizing factor\n$c^{1+O_{1} -O_{2}}$ , the resulting polynomial\ncan be divided by $Q$ by using the\ndiv_terms\nalgorithm without introducing any fractions.", + "token_count": 288, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Example: Symbolic Algebra", + "chunk_index": 11, + "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_11" + }, + { + "content": "Then it can be shown that, if we multiply\n$P$ by the\nintegerizing factor\n$c^{1+O_{1} -O_{2}}$ , the resulting polynomial\ncan be divided by $Q$ by using the\ndiv_terms\nalgorithm without introducing any fractions.\n\nThe operation of multiplying\nthe dividend by this constant and then dividing is sometimes called the\npseudodivision of $P$ by\n$Q$.\n\nThe remainder of the division is\ncalled the\npseudoremainder.\n\nThus, here is how to reduce a rational function to lowest terms:\n-\n-\nCompute the GCD of the numerator and denominator, using\nthe version of\ngcd_@terms\nfrom exercise.\n-\n-\nWhen you obtain the GCD, multiply both numerator and\ndenominator by the same integerizing factor before dividing through by\nthe GCD, so that division by the GCD will not introduce any noninteger\ncoefficients.\n\nAs the factor you can use the leading coefficient of\nthe GCD raised to the power\n$1+O_{1} -O_{2}$ , where\n$O_{2}$ is the order of the GCD and\n$O_{1}$ is the maximum of the orders of the\nnumerator and denominator.\n\nThis will ensure that dividing the\nnumerator and denominator by the GCD will not introduce any fractions.\n-\n-\nThe result of this operation will be a numerator and denominator\nwith integer coefficients.\n\nThe coefficients will normally be very\nlarge because of all of the integerizing factors, so the last step is\nto remove the redundant factors by computing the (integer) greatest\ncommon divisor of all the coefficients of the numerator and the\ndenominator and dividing through by this factor.\n\nThe GCD computation is at the heart of any system that does operations\non rational functions.\n\nThe algorithm used above, although\nmathematically straightforward, is extremely slow.\n\nThe slowness is\ndue partly to the large number of division operations and partly to\nthe enormous size of the intermediate coefficients generated by the\npseudodivisions.", + "token_count": 300, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Example: Symbolic Algebra", + "chunk_index": 12, + "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_12" + }, + { + "content": "The task of designing generic arithmetic operations is analogous to that of\ndesigning the generic complex-number operations.\n\nWe would like, for\ninstance, to have a generic addition\nfunction\nadd_rat\non rational numbers, and like\nadd_complex\non complex numbers.\n\nWe can implement following the same strategy we\nused in section to implement the\ngeneric selectors for complex numbers.\n\nWe will attach a type tag to each\nkind of number and cause the generic\nfunction\nto dispatch to an appropriate package according to the data type of its\narguments.\n\nThe generic arithmetic functions are defined as follows:\n\n```javascript\nops\n apply_generic\n\nfunction add(x, y) { return apply_generic(\"add\", list(x, y)); }\n\nfunction sub(x, y) { return apply_generic(\"sub\", list(x, y)); }\n\nfunction mul(x, y) { return apply_generic(\"mul\", list(x, y)); }\n\nfunction div(x, y) { return apply_generic(\"div\", list(x, y)); }\n```\n\nWe begin\nby installing a package for handling\nordinary numbers,\nthat is, the primitive numbers of our language.\n\nWe\ntag these\nwith the\nstring \"javascript_number\".\n\nThe arithmetic operations in this package are the primitive arithmetic\nfunctions\n(so there is no need to define extra\nfunctions\nto handle the untagged numbers).\n\nSince these operations each take two\narguments, they are installed in the table keyed by the list\nlist(\"javascript_number\", \"javascript_number\"):\n\n```javascript\ninstall_javascript_number_package\n ops\n operation_table_from_chapter_3\n operation_table\n attach_tag\n\nfunction install_javascript_number_package() {\n function tag(x) {\n return attach_tag(\"javascript_number\", x);\n }\n put(\"add\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x + y));\n put(\"sub\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x - y));\n put(\"mul\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x * y));\n put(\"div\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x / y));\n put(\"make\", \"javascript_number\",\n x => tag(x));\n return \"done\";\n}\n```\n\nUsers of the JavaScript-number package will create (tagged) ordinary numbers by means of the function:\n\n```javascript\nactually_install_javascript_number_package\n\ninstall_javascript_number_package();\n```\n\n```javascript\ninstall_javascript_number_package_usage\n install_javascript_number_package\n actually_install_javascript_number_package\n install_javascript_number_package_usage_example\n [ 'javascript_number', 9 ]\n\nfunction make_javascript_number(n) {\n return get(\"make\", \"javascript_number\")(n);\n}\n```", + "token_count": 301, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Generic Arithmetic Operations", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Data_Generic_Arithmetic_Operations_1" + }, + { + "content": "Users of the JavaScript-number package will create (tagged) ordinary numbers by means of the function:\n\n```javascript\ninstall_javascript_number_package_usage_example\n\nconst n1 = make_javascript_number(4);\nconst n2 = make_javascript_number(5);\n\nadd(n1, n2);\n```\n\nNow that the framework of the generic arithmetic system is in place,\nwe can readily include new kinds of numbers.\n\nHere is a package that\nperforms rational arithmetic.\n\nNotice that, as a benefit of\nadditivity, we can use without modification the rational-number code\nfrom section as the internal\nfunctions\nin the package:\n\n```javascript\nbenefit_of_additivity\n ops\n operation_table_from_chapter_3\n operation_table\n attach_tag\n gcd_definition\n benefit_of_additivity_example\n [ 'rational', [ 11, 15 ] ]\n\nfunction install_rational_package() {\n // internal functions\n function numer(x) { return head(x); }\n function denom(x) { return tail(x); }\n function make_rat(n, d) {\n const g = gcd(n, d);\n return pair(n / g, d / g);\n }\n function add_rat(x, y) {\n return make_rat(numer(x) * denom(y) + numer(y) * denom(x),\n denom(x) * denom(y));\n }\n function sub_rat(x, y) {\n return make_rat(numer(x) * denom(y) - numer(y) * denom(x),\n denom(x) * denom(y));\n }\n function mul_rat(x, y) {\n return make_rat(numer(x) * numer(y),\n denom(x) * denom(y));\n }\n function div_rat(x, y) {\n return make_rat(numer(x) * denom(y),\n denom(x) * numer(y));\n }\n // interface to rest of the system\n function tag(x) {\n return attach_tag(\"rational\", x);\n }\n put(\"add\", list(\"rational\", \"rational\"),\n (x, y) => tag(add_rat(x, y)));\n put(\"sub\", list(\"rational\", \"rational\"),\n (x, y) => tag(sub_rat(x, y)));\n put(\"mul\", list(\"rational\", \"rational\"),\n (x, y) => tag(mul_rat(x, y)));\n put(\"div\", list(\"rational\", \"rational\"),\n (x, y) => tag(div_rat(x, y)));\n put(\"make\", \"rational\",\n (n, d) => tag(make_rat(n, d)));\n return \"done\";\n}\n\nfunction make_rational(n, d) {\n return get(\"make\", \"rational\")(n, d);\n}\n```\n\n```javascript\nbenefit_of_additivity_example\n\ninstall_rational_package();\n\nconst r1 = make_rational(1, 3);\nconst r2 = make_rational(2, 5);\n\nadd(r1, r2);\n```\n\nWe can install a similar package to handle complex numbers, using the tag\n\"complex\".", + "token_count": 282, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Generic Arithmetic Operations", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Data_Generic_Arithmetic_Operations_2" + }, + { + "content": "We can install a similar package to handle complex numbers, using the tag\n\"complex\".\n\nIn creating the package, we extract from the table the operations\nmake_from_real_imag\nand\nmake_from_mag_ang\nthat were defined by the rectangular and polar packages.\nadd_complex,\nsub_complex,\nmul_complex,\nand\ndiv_complex\nfunctions\nfrom section.\n\n```javascript\ninstall_complex_package\n ops\n generic_selectors\n operation_table_from_chapter_3\n operation_table\n install_rectangular_package\n install_rectangular_package_usage\n install_polar_package_usage\n attach_tag\n actually_install_complex_package\n 'done'\n\nfunction install_complex_package() {\n // imported functions from rectangular and polar packages\n function make_from_real_imag(x, y) {\n return get(\"make_from_real_imag\", \"rectangular\")(x, y);\n }\n function make_from_mag_ang(r, a) {\n return get(\"make_from_mag_ang\", \"polar\")(r, a);\n }\n // internal functions\n function add_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) + real_part(z2),\n imag_part(z1) + imag_part(z2));\n }\n function sub_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) - real_part(z2),\n imag_part(z1) - imag_part(z2));\n }\n function mul_complex(z1, z2) {\n return make_from_mag_ang(magnitude(z1) * magnitude(z2),\n angle(z1) + angle(z2));\n }\n function div_complex(z1, z2) {\n return make_from_mag_ang(magnitude(z1) / magnitude(z2),\n angle(z1) - angle(z2));\n }\n // interface to rest of the system\n function tag(z) { return attach_tag(\"complex\", z); }\n put(\"add\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(add_complex(z1, z2)));\n put(\"sub\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(sub_complex(z1, z2)));\n put(\"mul\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(mul_complex(z1, z2)));\n put(\"div\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(div_complex(z1, z2)));\n put(\"make_from_real_imag\", \"complex\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"complex\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\n```\n\nPrograms outside the complex-number package can construct complex\nnumbers either from real and imaginary parts or from magnitudes and\nangles.\n\nNotice how the underlying\nfunctions,\noriginally defined in the rectangular and polar packages, are exported to\nthe complex package, and exported from there to the outside world.\n\n```javascript\nactually_install_complex_package\n\ninstall_complex_package();\n```\n\n```javascript\ninstall_complex_package_usage\n install_complex_package\n actually_install_complex_package\n [ 'rectangular', [ 8.387912809451864, 5.397127693021015 ] ]\n install_complex_package_example\n\nfunction make_complex_from_real_imag(x, y){\n return get(\"make_from_real_imag\", \"complex\")(x, y);\n}\nfunction make_complex_from_mag_ang(r, a){\n return get(\"make_from_mag_ang\", \"complex\")(r, a);\n}\n```", + "token_count": 281, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Generic Arithmetic Operations", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Data_Generic_Arithmetic_Operations_3" + }, + { + "content": "Notice how the underlying\nfunctions,\noriginally defined in the rectangular and polar packages, are exported to\nthe complex package, and exported from there to the outside world.\n\n```javascript\ninstall_complex_package_example\n\nconst r = make_complex_from_real_imag(4, 3);\nconst p = make_complex_from_mag_ang(5, 0.5);\n\nadd(r, p); // results in a complex number in rectangular coordinates\n// mul(r, p); // results in a complex number in polar coordinates\n\nconst r = make_complex_from_real_imag(4, 3);\nconst p = make_complex_from_mag_ang(5, 0.5);\ntail(add(r, p));\n```\n\nWhat we have here is a\n$3+4i$ in rectangular form, would be\nrepresented as shown in\nfigure.\n\nThe outer tag\n(\"complex\")\nis used to direct the number to the complex package.\n\nOnce within the\ncomplex package, the next tag\n(\"rectangular\")\nis used to direct the number to the rectangular package.\n\nIn a large and\ncomplicated system there might be many levels, each interfaced with the\nnext by means of generic operations.\n\nAs a data object is passed\ndownward, the outer tag that is used to direct it to the\nappropriate package is stripped off (by applying\n\n```javascript\nRepresentation of $3+4i$ in\n\t rectangular form.\n```\n\nIn the above packages, we used\nadd_rat,\nadd_complex,\nand the other arithmetic\nfunctions\nexactly as originally written.\n\nOnce these declarations are internal to\ndifferent installation\nfunctions,\nhowever, they no longer need names that are distinct from each other:\nwe could simply name them", + "token_count": 222, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Systems with Generic Operations", + "subsection": "Generic Arithmetic Operations", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Data_Generic_Arithmetic_Operations_4" + }, + { + "content": "We are about to study the idea of a\ncomputational process.\ndata.\nprogram.\n\nPeople create programs to direct processes.\n\nIn effect, we conjure the spirits of the computer with our spells.\n\nA computational process is indeed much like a sorcerer s idea of a\nspirit.\n\nIt cannot be seen or touched.\n\nIt is not composed of matter\nat all.\n\nHowever, it is very real.\n\nIt can perform intellectual work.\n\nIt can answer questions.\n\nIt can affect the world by disbursing money\nat a bank or by controlling a robot arm in a factory.\n\nThe programs we\nuse to conjure processes are like a sorcerer s spells.\n\nThey are\ncarefully composed from symbolic expressions in arcane and esoteric\nprogramming languages\n\nA computational process, in a correctly working computer, executes\nprograms precisely and accurately.\n\nThus, like the sorcerer s\napprentice, novice programmers must learn to understand and to\nanticipate the consequences of their conjuring.\n\nEven small errors\n(usually called bugs)\nin programs can have complex and unanticipated consequences.\n\nFortunately, learning to program is considerably less dangerous than\nlearning sorcery, because the spirits we deal with are conveniently\ncontained in a secure way.\n\nReal-world programming, however,\nrequires care, expertise, and wisdom.\n\nA small bug in a computer-aided\ndesign program, for example, can lead to the catastrophic collapse of\nan airplane or a dam or the self-destruction of an industrial robot.\n\nMaster software engineers have the ability to organize programs so\nthat they can be reasonably sure that the resulting processes will\nperform the tasks intended.\n\nThey can visualize the behavior of their\nsystems in advance.\n\nThey know how to structure programs so that\nunanticipated problems do not lead to catastrophic consequences, and\nwhen problems do arise, they can\ndebug\ntheir programs.", + "token_count": 288, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": null, + "subsection": null, + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Building_Abstractions_with_Functions_1" + }, + { + "content": "They know how to structure programs so that\nunanticipated problems do not lead to catastrophic consequences, and\nwhen problems do arise, they can\ndebug\ntheir programs.\n\nWell-designed\ncomputational systems, like well-designed automobiles or nuclear\nreactors, are designed in a modular manner, so that the parts can be\nconstructed, replaced, and debugged separately.\n\nWe need an appropriate language for describing processes, and we will\nuse for this purpose the programming language JavaScript.\n\nJust as our\neveryday thoughts are usually expressed in our natural language (such\nas English, Swedish, or Chinese), and descriptions of quantitative\nphenomena are expressed with mathematical notations, our procedural\nthoughts will be expressed in JavaScript.\n\nMocha , which\nwas later renamed to LiveScript , and finally to JavaScript.\n\nJavaScript is a trademark\nof Oracle Corporation.\n\nDespite its inception as a language for scripting the web, JavaScript interpreter is a machine that carries out processes described in the JavaScript language.\n\nJavaScript bears only superficial resemblance to the language Java,\nafter which it was\n(eventually) named; both Java and JavaScript use the block structure of\nthe language C.\n\nIn contrast with Java and C, which usually\nemploy compilation to lower-level\nlanguages, JavaScript programs were initially\ninterpreted\nby web browsers.\ns Internet Explorer, whose\nJavaScript version is called\nJScript.\n\nThe popularity of JavaScript for controlling web\nbrowsers gave rise to a standardization effort, culminating in\nECMAScript.\n\nThe\nand completed in June 1997\n(\n\nThe practice of embedding JavaScript programs in web pages encouraged\nthe developers of web browsers to implement JavaScript interpreters.\n\nAs these programs became more complex,\nthe interpreters became more efficient in executing them, eventually\nusing sophisticated implementation techniques such as Just-In-Time\n(JIT) compilation.", + "token_count": 275, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": null, + "subsection": null, + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Building_Abstractions_with_Functions_2" + }, + { + "content": "As these programs became more complex,\nthe interpreters became more efficient in executing them, eventually\nusing sophisticated implementation techniques such as Just-In-Time\n(JIT) compilation.\n\nThe majority of JavaScript programs as of this writing (2021) are embedded\nin web pages and interpreted by browsers, but JavaScript is increasingly\nused as a general-purpose programming language, using systems such as\nNode.js.", + "token_count": 58, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": null, + "subsection": null, + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Functions_Building_Abstractions_with_Functions_3" + }, + { + "content": "We have seen that\nfunctions\nare, in effect, abstractions that describe compound operations on\nnumbers independent of the particular numbers.\n\nFor example, when we\ndeclare\n\n```javascript\ncube_definition\n cube_example\n\nfunction cube(x) {\n return x * x * x;\n}\n```\n\n```javascript\ncube_example\n\t 27\n cube_definition\n\ncube(3);\n```\n\nwe are not talking about the cube of a particular number, but rather\nabout a method for obtaining the cube of any number.\n\nOf course we could\nget along without ever\ndeclaring this function,\nby always writing expressions such as\n\n```javascript\n3 * 3 * 3\nx * x * x\ny * y * y\n```\n\nand never mentioning\nFunctions\nprovide this ability.\n\nThis is why all but the most primitive\nprogramming languages include mechanisms for\ndeclaring functions.\n\nYet even in numerical processing we will be severely limited in our\nability to create abstractions if we are restricted to\nfunctions\nwhose parameters must be numbers.\n\nOften the same programming pattern\nwill be used with a number of different\nfunctions.\n\nTo express such patterns as concepts, we will need to construct\nfunctions\nthat can accept\nfunctions\nas arguments or return\nfunctions\nas values.\n\nFunctions\nthat manipulate\nfunctions\nare called\nhigher-order functions.\n\nThis section shows how higher-order\nfunctions\ncan serve as powerful abstraction mechanisms, vastly increasing the\nexpressive power of our language.", + "token_count": 216, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Formulating_Abstractions_with_Higher-Order_Functions_1" + }, + { + "content": "```javascript\nIn using sum as in\n\tsection, it seems\n\tterribly awkward to have to declare trivial functions such as\n pi_term and\n\tpi_next just so we can use them as\n\targuments to our higher-order function. Rather than declare\n\tpi_next and\n pi_term, it would be more convenient\n to have a way to directly specify the function that returns its\n input incremented by 4 and the function that returns the\n reciprocal of its input times its input plus 2. We can do this\n\tby introducing the lambda expression as a syntactic form for\n\tcreating functions.\n\tUsing lambda expressions, we can describe what we want as\n\nx => x + 4\n\n and\n\nx => 1 / (x * (x + 2))\n```\n\n```javascript\nThen we can express our\n\tpi_sum\n\tfunction\n```\n\nwithout declaring any auxiliary functions:\n\n```javascript\npi_sum_definition3\n 3.139592655589783\n sum_definition\n pi_sum_example\n 3.139592655589783\n\nfunction pi_sum(a, b) {\n return sum(x => 1 / (x * (x + 2)),\n a,\n x => x + 4,\n b);\n}\n```\n\nAgain using a lambda expression, we can write the function without having to declare the auxiliary function add_dx:\n\n```javascript\nintegral_example_2\n\nintegral(cube, 0, 1, 0.01);\n```\n\n```javascript\nintegral_definition2\n sum_definition\n cube_definition\n integral_example_2\n 0.24998750000000042\n\nfunction integral(f, a, b, dx) {\n return sum(f,\n a + dx / 2,\n x => x + dx,\n b)\n *\n dx;\n}\n```\n\n```javascript\nIn general, lambda expressions are used to create functions in the\n\tsame way as function declarations,\n\treturn keyword and braces are omitted\n\t(if there is only one\n\tparameter, the\n\n(parameters) => expression\n\n\tThe resulting function is just as much a function\n\tas one that is created using a function declaration statement.\n```\n\nWe consider\n\n```javascript\nplus4_definition_1\n plus4_example\n\nfunction plus4(x) {\n return x + 4;\n}\n```\n\n```javascript\nplus4_example\n\nplus4(3);\n```\n\n```javascript\nto be\n\tequivalent to\n```\n\n```javascript\nplus4_definition_2\n plus4_example\n\nconst plus4 = x => x + 4;\n```", + "token_count": 304, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Constructing Functions using Lambda Expressions", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Constructing_Functions_using_Lambda_Expressions_1" + }, + { + "content": "We consider\n\n```javascript\nWe can read a lambda expression as follows:\n\n\\begin{flushleft}\\normalcodesize\n\\begin{tabular}{@{}ccccc}\n \\tt x & \\tt => & \\tt x & \\tt + & \\tt 4 \\\\\n $\\Big\\uparrow$ & $\\Big\\uparrow$ & $\\Big\\uparrow$ & $\\Big\\uparrow$ & $\\Big\\uparrow$ \\\\[4pt]\n \\normalsize The function of an argument \\small\\tt x & \\normalsize that results in & \\normalsize the value & \\normalsize plus & \\normalsize 4. \\\\\n\\end{tabular}\n\\end{flushleft}\n```\n\nLike any expression that has a function as its value, a lambda expression can be used as the function expression in an application such as\n\n```javascript\nsquare_definition\n 12\n\n((x, y, z) => x + y + square(z))(1, 2, 3);\n```\n\nor, more generally, in any context where we would normally use a function name.\n\n```javascript\nNote that =>\n has\n```\n\nAnother use of lambda expressions is in creating local names.\n\n```javascript\nWe often need local names in our functions\n\tother than those that have been bound as parameters.\n```\n\nFor example, suppose we wish to compute the function\n\\[\\begin{array}{lll}\nf(x, y)&=&x(1 + x y)^2 +y (1 - y) + (1 + x y)(1 - y)\n\\end{array}\\]\nwhich we could also express as\n\\[\\begin{array}{rll}\na &=& 1+xy\\\\\nb &=& 1-y\\\\\nf(x, y) &= &x a^2 +y b + a b\n\\end{array}\\]\nIn writing a\nfunction\nto compute $f$ , we would like to include as\nlocal names\nnot only $x$ and $y$\nbut also the names of intermediate quantities like\n$a$ and $b$.\n\nOne way\nto accomplish this is to use an auxiliary\nfunction to bind the local names:\n\n```javascript\nf_helper_definition\n square_definition\n f_helper_example\n 456\n\nfunction f(x, y) {\n function f_helper(a, b) {\n return x * square(a) + y * b + a * b;\n }\n return f_helper(1 + x * y, 1 - y);\n}\n```\n\n```javascript\nf_helper_example\n\nf(3, 4);\n```", + "token_count": 295, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Constructing Functions using Lambda Expressions", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Constructing_Functions_using_Lambda_Expressions_2" + }, + { + "content": "One way\nto accomplish this is to use an auxiliary\nfunction to bind the local names:\n\nOf course, we could use a\nlambda\nexpression to specify an anonymous\nfunction for binding our local names.\n\nThe\nfunction body\nthen becomes a single call to that\nfunction:\n\n```javascript\nf_helper_definition2\n square_definition\n f_2_helper_example\n 456\n\nfunction f_2(x, y) {\n return ( (a, b) => x * square(a) + y * b + a * b\n )(1 + x * y, 1 - y);\n}\n```\n\n```javascript\nf_2_helper_example\n\nf_2(3, 4);\n```\n\n```javascript\nconst, the function\n\n\tcan be written as\n\n f_helper_definition3\n square_definition\n f_3_helper_example\n\t 456\n\nfunction f_3(x, y) {\n const a = 1 + x * y;\n const b = 1 - y;\n return x * square(a) + y * b + a * b;\n}\n\n\t f_3_helper_example\n\nf_3(3, 4);\n\n Names that are declared with\n const inside a block have the\n body of the immediately surrounding block as their scope.$^,$\n```\n\nConditional statements\nWe have seen that it is often useful to declare names that are local to\nfunction declarations.\n\nWhen functions become big, we should\nkeep the scope of the names as narrow as possible.\n\nConsider for example expmod in\nexercise.\n\n```javascript\nexpmod_definition_2\n even_definition\n expmod_example_2\n\t4\n\nfunction expmod(base, exp, m) {\n return exp === 0\n ? 1\n : is_even(exp)\n ? ( expmod(base, exp / 2, m)\n * expmod(base, exp / 2, m)) % m\n : (base * expmod(base, exp - 1, m)) % m;\n}\n```\n\nThis function is unnecessarily inefficient, because it contains two identical calls:\n\n```javascript\nexpmod_example_2\n even_definition\n expmod_definition_2\n\nexpmod(base, exp / 2, m);\n\nexpmod(4, 3, 5);\n```\n\nWhile this can be easily fixed in this example using the\nsquare function, this is not so easy\nin general.\n\nWithout using square ,\nwe would be tempted to introduce a local name for the expression as\nfollows:", + "token_count": 300, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Constructing Functions using Lambda Expressions", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Functions_Constructing_Functions_using_Lambda_Expressions_3" + }, + { + "content": "Without using square ,\nwe would be tempted to introduce a local name for the expression as\nfollows:\n\n```javascript\neven_definition\n expmod_example\n\nfunction expmod(base, exp, m) {\n const half_exp = expmod(base, exp / 2, m);\n return exp === 0\n ? 1\n : is_even(exp)\n ? (half_exp * half_exp) % m\n : (base * expmod(base, exp - 1, m)) % m;\n}\n```\n\nexp === 0 is met.\n\nTo avoid this situation, we provide for\nconditional statements , and allow return\nstatements to appear in the branches of the statement.\n\nUsing a\nconditional statement, we can write the function\nexpmod as follows:\n\n```javascript\neven_definition\n expmod_example\n\t4\n\nfunction expmod(base, exp, m) {\n if (exp === 0) {\n return 1;\n } else {\n if (is_even(exp)) {\n const half_exp = expmod(base, exp / 2, m);\n return (half_exp * half_exp) % m;\n } else {\n return (base * expmod(base, exp - 1, m)) % m;\n }\n }\n}\n```\n\nThe general form of a conditional statement is\n\n```javascript\nif (predicate) { consequent-statements } else { alternative-statements }\n```\n\nAs for a conditional expression, the interpreter first evaluates the\npredicate.\n\nIf it evaluates to true,\nthe interpreter evaluates the\nconsequent-statements in sequence, and if it\nevaluates to false, the interpreter evaluates\nalternative-statements in sequence.\n\nEvaluation of a return\nstatement returns from the surrounding function, ignoring any\nstatements in the sequence\n\nLet's use the substitution model to illustrate what happens:\n\n```javascript\nf(f)\nf(2)\n2(2)\n```\n\nThe application 2(2) leads to an error, since 2 is neither a primitive nor a compound function.", + "token_count": 253, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Constructing Functions using Lambda Expressions", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Functions_Constructing_Functions_using_Lambda_Expressions_4" + }, + { + "content": "Consider the following three\nfunctions.\n\nThe first computes the sum of the integers from\n\n```javascript\nsum_integers_definition\n sum_integers_example\n 55\n\nfunction sum_integers(a, b) {\n return a > b\n ? 0\n : a + sum_integers(a + 1, b);\n}\n```\n\n```javascript\nsum_integers_example\n\nsum_integers(1, 10);\n```\n\nThe second computes the sum of the cubes of the integers in the given range:\n\n```javascript\nsum_cubes_definition\n cube_definition\n sum_cubes_example\n 775\n\nfunction sum_cubes(a, b) {\n return a > b\n ? 0\n : cube(a) + sum_cubes(a + 1, b);\n}\n```\n\n```javascript\nsum_cubes_example\n\nsum_cubes(3, 7);\n```\n\nThe third computes the sum of a sequence of terms in the series \\[ \\frac{1}{1\\cdot3}+\\frac{1}{5\\cdot7}+\\frac{1}{9\\cdot11}+\\cdots \\] which converges to $\\pi/8$ (very slowly):\n\n```javascript\npi_sum_definition\n pi_sum_example\n 3.139592655589783\n\nfunction pi_sum(a, b) {\n return a > b\n ? 0\n : 1 / (a * (a + 2)) + pi_sum(a + 4, b);\n}\n```\n\n```javascript\npi_sum_example\n\n8 * pi_sum(1, 1000);\n```\n\nThese three\nfunctions\nclearly share a common underlying pattern.\n\nThey are for the most part\nidentical, differing only in the name of the\nfunction,\nthe function of\nfunctions\nby filling in slots in the same template:\n\n```javascript\nfunction name(a, b) {\n return a > b\n ? 0\n : term(a) + name(next(a), b);\n}\n```\n\nThe presence of such a common pattern is strong evidence that there is a\nuseful\nsummation of a series and invented sigma\nnotation, for example\n\\[\\begin{array}{lll}\n\\displaystyle\\sum_{n=a}^{b}\\ f(n)&=&f(a)+\\cdots+f(b)\n\\end{array}\\]\nto express this concept.\n\nThe power of sigma notation is that it allows\nmathematicians to deal with the concept of summation itself rather than only\nwith particular sums for example, to formulate general results about\nsums that are independent of the particular series being summed.\n\nSimilarly, as program designers, we would like our language to be powerful\nenough so that we can write a\nfunction\nthat expresses the concept of summation itself rather than only\nfunctions\nthat compute particular sums.", + "token_count": 307, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Functions as Arguments", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Arguments_1" + }, + { + "content": "Similarly, as program designers, we would like our language to be powerful\nenough so that we can write a\nfunction\nthat expresses the concept of summation itself rather than only\nfunctions\nthat compute particular sums.\n\nWe can do so readily in our\nfunctional\nlanguage by taking the common template shown above and transforming the\nslots into\nparameters:\n\n```javascript\nsum_definition\n\nfunction sum(term, a, next, b) {\n return a > b\n ? 0\n : term(a) + sum(term, next(a), next, b);\n}\n```\n\nNotice that\nfunctions\nfunction.\n\nFor example, we can use it (along with a\nfunction\nsum_cubes:\n\n```javascript\ninc_definition\n\nfunction inc(n) {\n return n + 1;\n}\n```\n\n```javascript\nsum_example\n cube_definition\n sum_definition\n sum_example_example\n 3025\n\nfunction inc(n) {\n return n + 1;\n}\nfunction sum_cubes(a, b) {\n return sum(cube, a, inc, b);\n}\n```\n\nUsing this, we can compute the sum of the cubes of the integers from 1 to 10:\n\n```javascript\nsum_example_example\n sum_example\n\nsum_cubes(1, 10);\n```\n\nWith the aid of an identity function to compute the term, we can define sum_@integers in terms of\n\n```javascript\nidentity\n identity_example\n\nfunction identity(x) {\n return x;\n}\n```\n\n```javascript\nidentity_example\n\nidentity(42);\n```\n\n```javascript\nsum_integers_definition2\n sum_definition\n inc_definition\n identity\n sum_integers_example2\n 55\n\nfunction sum_integers(a, b) {\n return sum(identity, a, inc, b);\n}\n```\n\nThen we can add up the integers from 1 to 10:\n\n```javascript\nsum_integers_example2\n sum_integers_definition2\n\nsum_integers(1, 10);\n```\n\nWe can also define pi_sum in the same way:\n\n```javascript\npi_sum_definition2\n sum_definition\n pi_sum_example2\n 3.139592655589783\n\nfunction pi_sum(a, b) {\n function pi_term(x) {\n return 1 / (x * (x + 2));\n }\n function pi_next(x) {\n return x + 4;\n }\n return sum(pi_term, a, pi_next, b);\n}\n```\n\nUsing these functions, we can compute an approximation to $\\pi$ :\n\n```javascript\npi_sum_example2\n pi_sum_definition2\n\n8 * pi_sum(1, 1000);\n```", + "token_count": 285, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Functions as Arguments", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Arguments_2" + }, + { + "content": "Using these functions, we can compute an approximation to $\\pi$ :\n\nOnce we have $f$ between the\nlimits $a$ and $b$ can\nbe approximated numerically using the formula\n\\[\n\\begin{array}{lll}\n\\displaystyle\\int_{a}^{b}f & = &\n\\left[\\,f\\!\\left( a+\\dfrac{dx}{2} \\right)\\,+\\,f\\!\\left(a+dx+\\dfrac{dx}{2}\n\\right)\\,+\\,f\\!\\left( a+2dx+\\dfrac{dx}{2}\\right)\\,+\\,\\cdots\n\\right] dx\n\\end{array}\n\\]\nfor small values of $dx$.\n\nWe can express this\ndirectly as a\nfunction:\n\n```javascript\nintegral_definition\n sum_definition\n integral_example\n 0.24998750000000042\n\nfunction integral(f, a, b, dx) {\n function add_dx(x) {\n return x + dx;\n }\n return sum(f, a + dx / 2, add_dx, b) * dx;\n}\n```\n\n```javascript\nintegral_example\n cube_definition\n integral_definition\n\nintegral(cube, 0, 1, 0.01);\n```\n\n```javascript\nintegral_example2\n cube_definition\n integral_definition\n\nintegral(cube, 0, 1, 0.001);\n\n// dare to try 0.001 with in browser?\nintegral(cube, 0, 1, 0.002);\n```\n\n(The exact value of the integral of", + "token_count": 125, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Functions as Arguments", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Arguments_3" + }, + { + "content": "We introduced compound\nfunctions\nin section as a mechanism for\nabstracting patterns of numerical operations so as to make them independent\nof the particular numbers involved.\n\nWith higher-order\nfunctions,\nsuch as\nthe\nfunction\nof section , we began to\nsee a more powerful kind of abstraction:\nfunctions\nused to express general methods of computation, independent of the\nparticular functions involved.\n\nIn this section we discuss two more elaborate\nexamples general methods for finding zeros and fixed points of\nfunctions and show how these methods can be expressed directly as\nfunctions.\n\nThe\nhalf-interval method is a simple but powerful technique for\nfinding roots of an equation $f(x)=0$ , where\n$f$ is a continuous function.\n\nThe idea is that,\nif we are given points $a$ and\n$b$ such that\n$f(a) < 0 < f(b)$ , then\n$f$ must have at least one zero between\n$a$ and $b$.\n\nTo locate\na zero, let $x$ be the average of\n$a$ and $b$ and\ncompute $f(x)$.\n\nIf\n$f(x) > 0$ , then\n$f$ must have a zero between\n$a$ and $x$.\n\nIf\n$f(x) < 0$ , then\n$f$ must have a zero between\n$x$ and $b$.\n\nContinuing in this way, we can identify smaller and smaller intervals on\nwhich $f$ must have a zero.\n\nWhen we reach a\npoint where the interval is small enough, the process stops.\n\nSince the\ninterval of uncertainty is reduced by half at each step of the process, the\nmaximal number of steps required grows as\n$\\Theta(\\log( L/T))$ , where\n$L$ is the length of the original interval and\n$T$ is the error tolerance (that is, the size of\nthe interval we will consider small enough ).\n\nHere is a\nfunction that implements this strategy:", + "token_count": 285, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Functions as General Methods", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_General_Methods_1" + }, + { + "content": "Here is a\nfunction that implements this strategy:\n\n```javascript\npositive_negative_definition\n\nfunction positive(x) { return x > 0; }\nfunction negative(x) { return x < 0; }\n```\n\n```javascript\nsearch_definition\n average_definition\n positive_negative_definition\n close_enough_definition\n search_example\n 1\n\nfunction search(f, neg_point, pos_point) {\n const midpoint = average(neg_point, pos_point);\n if (close_enough(neg_point, pos_point)) {\n return midpoint;\n } else {\n const test_value = f(midpoint);\n return positive(test_value)\n ? search(f, neg_point, midpoint)\n : negative(test_value)\n ? search(f, midpoint, pos_point)\n : midpoint;\n }\n}\n```\n\n```javascript\nsearch_example\n\nsearch(x => x * x - 1, 0, 2);\n```\n\nWe assume that we are initially given the function\n$f$ together with points at which its values are\nnegative and positive.\n\nWe first compute the midpoint of the two given\npoints.\n\nNext we check to see if the given interval is small enough, and if\nso we simply return the midpoint as our answer.\n\nOtherwise, we compute as a\ntest value the value of $f$ at the midpoint.\n\nIf\nthe test value is positive, then we continue the process with a new interval\nrunning from the original negative point to the midpoint.\n\nIf the test value\nis negative, we continue with the interval from the midpoint to the positive\npoint.\n\nFinally, there is the possibility that the test value is 0, in\nwhich case the midpoint is itself the root we are searching for.\n\nTo test whether the endpoints are close enough we can use a\nfunction\nsimilar to the one used in section for\ncomputing square roots:\n\n```javascript\nclose_enough_definition\n abs_definition\n close_enough_example\n false\n\nfunction close_enough(x, y) {\n return abs(x - y) < 0.001;\n}\n```\n\n```javascript\nclose_enough_example\n\nclose_enough(7.7654, 7.7666);\n```\n\n```javascript\nThe function\n\tsearch\n```\n\nis awkward to use directly, because we can accidentally give it points at\nwhich $f$ s values do not have the required\nsign, in which case we get a wrong answer.", + "token_count": 302, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Functions as General Methods", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_General_Methods_2" + }, + { + "content": "is awkward to use directly, because we can accidentally give it points at\nwhich $f$ s values do not have the required\nsign, in which case we get a wrong answer.\n\nInstead we will use\nfunction,\nwhich checks to see which of the endpoints has a negative function value and\nwhich has a positive value, and calls the\nfunction\naccordingly.\n\nIf the function has the same sign on the two given points, the\nhalf-interval method cannot be used, in which case the\nfunction\nsignals an error.\n\n```javascript\nhalf_definition\n search_definition\n half_example\n 3.14111328125\n\nfunction half_interval_method(f, a, b) {\n const a_value = f(a);\n const b_value = f(b);\n return negative(a_value) && positive(b_value)\n ? search(f, a, b)\n : negative(b_value) && positive(a_value)\n ? search(f, b, a)\n : error(\"values are not of opposite sign\");\n}\n```\n\nThe following example uses the $\\pi$ as the root between 2 and 4 of $\\sin\\, x = 0$ :\n\n```javascript\nhalf_example\n half_definition\n\nhalf_interval_method(math_sin, 2, 4);\n```\n\nHere is another example, using the half-interval method to search for a root of the equation $x^3 - 2x - 3 = 0$ between 1\n\nand 2:\n\n```javascript\nhalf_example2\n half_definition\n 1.89306640625\n\nhalf_interval_method(x => x * x * x - 2 * x - 3, 1, 2);\n```\n\nA number $x$ is called a\nfixed point of a\nfunction $f$ if $x$\nsatisfies the equation $f(x)=x$.\n\nFor some\nfunctions $f$ we can locate a fixed point by\nbeginning with an initial guess and applying $f$\nrepeatedly,\n\\[\n\\begin{array}{l}\nf(x), \\ f(f(x)), \\ f(f(f(x))), \\ \\ldots\n\\end{array}\n\\]\nuntil the value does not change very much.\n\nUsing this idea, we can devise a\nfunction\nfixed_point\nthat takes as inputs a function and an initial guess and produces an\napproximation to a fixed point of the function.", + "token_count": 289, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Functions as General Methods", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_General_Methods_3" + }, + { + "content": "Using this idea, we can devise a\nfunction\nfixed_point\nthat takes as inputs a function and an initial guess and produces an\napproximation to a fixed point of the function.\n\nWe apply the function\nrepeatedly until we find two successive values whose difference is less\nthan some prescribed tolerance:\n\n```javascript\nfixed_definition\n abs_definition\n fixed_example\n 0.7390822985224023\n\nconst tolerance = 0.00001;\nfunction fixed_point(f, first_guess) {\n function close_enough(x, y) {\n return abs(x - y) < tolerance;\n }\n function try_with(guess) {\n const next = f(guess);\n return close_enough(guess, next)\n ? next\n : try_with(next);\n }\n return try_with(first_guess);\n}\n```\n\nFor example, we can use this method to approximate the fixed point of the\n\n```javascript\nfixed_example\n fixed_definition\n\nfixed_point(math_cos, 1);\n```\n\nSimilarly, we can find a solution to the equation $y=\\sin y + \\cos y$ :\n\n```javascript\nfixed_example2\n fixed_definition\n 1.2587315962971173\n\nfixed_point(y => math_sin(y) + math_cos(y), 1);\n```\n\nThe fixed-point process is reminiscent of the process we used for finding\nsquare roots in section.\n\nBoth are based on\nthe idea of repeatedly improving a guess until the result satisfies some\ncriterion.\n\nIn fact, we can readily formulate the\n$x$ requires finding a\n$y$ such that\n$y^2 = x$.\n\nPutting this equation into the\nequivalent form $y = x/y$ , we recognize that we\nare looking for a fixed point of the function $y \\mapsto x/y$ , and we can therefore try to\ncompute square roots as\n\n```javascript\nsqrt_definition2\n fixed_definition\n sqrt_example7\n\nfunction sqrt(x) {\n return fixed_point(y => x / y, 1);\n}\n```\n\n```javascript\nsqrt_example7\n\nsqrt(5);\n```\n\nUnfortunately, this fixed-point search does not converge.\n\nConsider an\ninitial guess $y_1$.\n\nThe next guess is\n$y_2 = x/y_1$ and the next guess is\n$y_3 = x/y_2 = x/(x/y_1) = y_1$.\n\nThis results\nin an infinite loop in which the two guesses\n$y_1$ and $y_2$ repeat\nover and over, oscillating about the answer.", + "token_count": 299, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Functions as General Methods", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_General_Methods_4" + }, + { + "content": "This results\nin an infinite loop in which the two guesses\n$y_1$ and $y_2$ repeat\nover and over, oscillating about the answer.\n\nOne way to control such oscillations is to prevent the guesses from changing\nso much.\n\nSince the answer is always between our guess\n$y$\nand $x/y$ , we can make a new guess that is not as\nfar from $y$ as $x/y$\nby averaging $y$ with\n$x/y$ , so that the next guess after\n$y$ is\n$\\frac{1}{2}(y+x/y)$ instead of\n$x/y$.\n\nThe process of making such a sequence of\nguesses is simply the process of looking for a fixed point of\n$y \\mapsto \\frac{1}{2}(y+x/y)$ :\n\n```javascript\nsqrt_definition3\n fixed_definition\n average_definition\n sqrt_example7\n 2.236067977499978\n\nfunction sqrt(x) {\n return fixed_point(y => average(y, x / y), 1);\n}\n```\n\n(Note that $y=\\frac{1}{2}(y+x/y)$ is a simple transformation of the equation $y=x/y$ ; to derive it, add $y$ to both sides of the equation and divide\n\nby 2.)\n\nWith this modification, the square-root\nfunction\nworks.\n\nIn fact, if we unravel the definitions, we can see that the sequence\nof approximations to the square root generated here is precisely the same as\nthe one generated by our original square-root\nfunction\nof section.\n\nThis approach of averaging\nsuccessive approximations to a solution, a technique we call\naverage damping , often aids the convergence of fixed-point searches.", + "token_count": 218, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Functions as General Methods", + "chunk_index": 5, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_General_Methods_5" + }, + { + "content": "The above examples demonstrate how the ability to pass\nfunctions\nas arguments significantly enhances the expressive power of our programming\nlanguage.\n\nWe can achieve even more expressive power by creating\nfunctions\nwhose returned values are themselves\nfunctions.\n\nWe can illustrate this idea by looking again at the fixed-point example\ndescribed at the end of\nsection.\n\nWe formulated a new\nversion of the square-root\nfunction\nas a fixed-point search, starting with the observation that\n$\\sqrt{x}$ is a fixed-point of the function\n$y\\mapsto x/y$.\n\nThen we used average damping to\nmake the approximations converge.\n\nAverage damping is a useful general\ntechnique in itself.\n\nNamely, given a\nfunction $f$ , we consider the function\nwhose value at $x$ is equal to the average of\n$x$ and $f(x)$.\n\nWe can express the idea of average damping by means of the following function:\n\n```javascript\naverage_damp_definition\n average_definition\n average_damp_example\n 55\n\nfunction average_damp(f) {\n return x => average(x, f(x));\n}\n```\n\n```javascript\nThe function\n average_damp\n```\n\ntakes as its argument a\nfunction\nfunction\n(produced by the lambda expression)\nthat, when applied to a number\nf(x).\n\nFor example, applying\naverage_damp\nto the\nfunction\nproduces a\nfunction\nwhose value at some number $x$ is the average of\n$x$ and $x^2$.\n\nApplying this resulting\nfunction\nto 10 returns the average of 10 and 100, or 55:\n\n```javascript\naverage_damp_example\n average_damp_definition\n square_definition\n\naverage_damp(square)(10);\n```\n\nUsing average_damp, we can reformulate the function as follows:\n\n```javascript\nsqrt_definition4\n average_damp_definition\n fixed_definition\n sqrt_example3\n\nfunction sqrt(x) {\n return fixed_point(average_damp(y => x / y), 1);\n}\n```\n\n```javascript\nsqrt_example3\n sqrt_definition4\n 2.4494897427875517\n\nsqrt(6);\n```\n\nNotice how this formulation makes explicit the three ideas in the method:\nfixed-point search, average damping, and the function\n$y\\mapsto x/y$.\n\nIt is instructive to compare\nthis formulation of the square-root method with the original version given\nin section.", + "token_count": 292, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Functions as Returned Values", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Returned_Values_1" + }, + { + "content": "It is instructive to compare\nthis formulation of the square-root method with the original version given\nin section.\n\nBear in mind that these\nfunctions\nexpress the same process, and notice how much clearer the idea becomes when\nwe express the process in terms of these abstractions.\n\nIn general, there\nare many ways to formulate a process as a\nfunction.\n\nExperienced programmers know how to choose\nprocess\nformulations that are particularly perspicuous, and where useful elements of\nthe process are exposed as separate entities that can be reused in other\napplications.\n\nAs a simple example of reuse, notice that the cube root of\n$x$ is a fixed point of the function\n$y\\mapsto x/y^2$ , so we can immediately\ngeneralize our square-root\nfunction\nto one that extracts\n\n```javascript\ncube_root_definition\n average_damp_definition\n fixed_definition\n square_definition\n cube_root_example\n\nfunction cube_root(x) {\n return fixed_point(average_damp(y => x / square(y)), 1);\n}\n```\n\n```javascript\ncube_root_example\n cube_root_definition\n 2.9999972321057697\n\ncube_root(27);\n```\n\nWhen we first introduced the square-root\nfunction,\nin section , we mentioned that this was a\nspecial case of\nNewton s method.\n\nIf\n$x\\mapsto g(x)$ is a differentiable function,\nthen a solution of the equation $g(x)=0$ is a\nfixed point of the function $x\\mapsto f(x)$ where\n\\[\n\\begin{array}{lll}\nf(x) & = & x - \\dfrac{g(x)}{Dg(x)}\n\\end{array}\n\\]\nand $Dg(x)$ is the derivative of\n$g$ evaluated at $x$.\ns method is the use of the fixed-point method we saw above to\napproximate a solution of the equation by finding a fixed point of the\nfunction $f$. $g$ and for sufficiently good\ninitial guesses for $x$ , Newton s method\nconverges very rapidly to a solution of\n$g(x)=0$.\n\nIn order to implement Newton s method as a\nfunction,\nwe must first express the idea of\nderivative, like average damping, is something that\ntransforms a function into another function.", + "token_count": 295, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Functions as Returned Values", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Returned_Values_2" + }, + { + "content": "In order to implement Newton s method as a\nfunction,\nwe must first express the idea of\nderivative, like average damping, is something that\ntransforms a function into another function.\n\nFor instance, the derivative\nof the function $x\\mapsto x^3$ is the function\n$x \\mapsto 3x^2$.\n\nIn general, if\n$g$ is a function and\n$dx$ is a small number, then the derivative\n$Dg$ of $g$ is the\nfunction whose value at any number $x$ is given\n(in the limit of small $dx$ ) by\n\\[\n\\begin{array}{lll}\nDg(x) & = & \\dfrac {g(x+dx) - g(x)}{dx}\n\\end{array}\n\\]\nThus, we can express the idea of derivative (taking\n$dx$ to be, say, 0.00001) as the\nfunction\n\n```javascript\nderiv_definition\n dx\n deriv_example\n\nfunction deriv(g) {\n return x => (g(x + dx) - g(x)) / dx;\n}\n```\n\nalong with the declaration\n\n```javascript\ndx\n\nconst dx = 0.00001;\n```\n\nLike\naverage_damp,\nfunction\nthat takes a\nfunction\nas argument and returns a\nfunction\nas value.\n\nFor example, to approximate the derivative of\n$x \\mapsto x^3$ at 5 (whose exact value is 75)\nwe can evaluate\n\n```javascript\nderiv_example\n deriv_definition\n 75.00014999664018\n\nfunction cube(x) { return x * x * x; }\n\nderiv(cube)(5);\n```\n\nWith the aid of s method as a fixed-point process:\n\n```javascript\nnewtons_method_definition\n fixed_definition\n deriv_definition\n sqrt_example4\n 2.4494897427970397\n\nfunction newton_transform(g) {\n return x => x - g(x) / deriv(g)(x);\n}\nfunction newtons_method(g, guess) {\n return fixed_point(newton_transform(g), guess);\n}\n```\n\nThe\nnewton_transform\nfunction\nexpresses the formula at the beginning of this section, and\nnewtons_method\nis readily defined in terms of this.\n\nIt takes as arguments a\nfunction\nthat computes the function for which we want to find a zero, together with\nan initial guess.\n\nFor instance, to find the\nsquare root of $x$ , we can use\ns\nmethod to find a zero of the function\n$y\\mapsto y^2-x$ starting with an initial guess\nof 1.\nfunction:", + "token_count": 306, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Functions as Returned Values", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Returned_Values_3" + }, + { + "content": "For instance, to find the\nsquare root of $x$ , we can use\ns\nmethod to find a zero of the function\n$y\\mapsto y^2-x$ starting with an initial guess\nof 1.\nfunction:\n\n```javascript\nsqrt_definition5\n newtons_method_definition\n square_definition\n sqrt_example4\n\nfunction sqrt(x) {\n return newtons_method(y => square(y) - x, 1);\n}\n```\n\n```javascript\nsqrt_example4\n sqrt_definition5\n\nsqrt(6);\n```\n\nWe ve seen two ways to express the square-root computation as an\ninstance of a more general method, once as a fixed-point search and once\nusing Newton s method.\n\nSince Newton s method was itself\nexpressed as a fixed-point process, we actually saw two ways to compute\nsquare roots as fixed points.\n\nEach method begins with a function and finds a\nfunction:\n\n```javascript\nfixed_point_of_transform_definition\n fixed_definition\n sqrt_example5\n 2.4494897427875517\n\nfunction fixed_point_of_transform(g, transform, guess) {\n return fixed_point(transform(g), guess);\n}\n```\n\nThis very general function takes as its arguments a function function that transforms\n\nUsing this abstraction, we can recast the first square-root computation $y \\mapsto x/y$ ) as an instance of this general method:\n\n```javascript\nsqrt_definition6\n fixed_point_of_transform_definition\n average_damp_definition\n sqrt_example5\n 2.4494897427875517\n\nfunction sqrt(x) {\n return fixed_point_of_transform(\n y => x / y,\n average_damp,\n 1);\n}\n```\n\n```javascript\nsqrt_example5\n sqrt_definition6\n\nsqrt(6);\n```\n\nSimilarly, we can express the second square-root computation from this section (an instance of s method that finds a fixed point of the Newton transform\n\nof $y\\mapsto y^2-x$ ) as\n\n```javascript\nsqrt_definition7\n fixed_point_of_transform_definition\n square_definition\n newtons_method_definition\n sqrt_example6\n\nfunction sqrt(x) {\n return fixed_point_of_transform(\n y => square(y) - x,\n newton_transform,\n 1);\n}\n```\n\n```javascript\nsqrt_example6\n sqrt_definition7\n\nsqrt(6);\n```\n\nWe began section with the\nobservation that compound\nfunctions\nare a crucial abstraction mechanism, because they permit us to express\ngeneral methods of computing as explicit elements in our programming\nlanguage.\n\nNow we ve seen how higher-order\nfunctions\npermit us to manipulate these general methods to create further abstractions.", + "token_count": 294, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Functions as Returned Values", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Returned_Values_4" + }, + { + "content": "Now we ve seen how higher-order\nfunctions\npermit us to manipulate these general methods to create further abstractions.\n\nAs programmers, we should be alert to opportunities to identify the\nunderlying abstractions in our programs and to build upon them and\ngeneralize them to create more powerful abstractions.\n\nThis is not to say\nthat one should always write programs in the most abstract way possible;\nexpert programmers know how to choose the level of abstraction appropriate\nto their task.\n\nBut it is important to be able to think in terms of these\nabstractions, so that we can be ready to apply them in new contexts.\n\nThe\nsignificance of higher-order\nfunctions\nis that they enable us to represent these abstractions explicitly as\nelements in our programming language, so that they can be handled just\nlike other computational elements.\n\nIn general, programming languages impose restrictions on the ways in which\ncomputational elements can be manipulated.\n\nElements with the fewest\nrestrictions are said to have\nfirst-class status.\n\nSome of the rights and\nprivileges of first-class elements are:\n-\n-\nThey may be referred to using names.\n-\n-\nThey may be passed as arguments to\nfunctions.\n-\n-\nThey may be returned as the results of\nfunctions.\n-\n-\nThey may be included in data structures.\n\nJavaScript,\nlike other high-level\nprogramming languages, awards\nfunctions\nfull first-class status.\n\nThis poses challenges for efficient\nimplementation, but the resulting gain in expressive power is\nenormous.", + "token_count": 238, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Functions as Returned Values", + "chunk_index": 5, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Returned_Values_5" + }, + { + "content": "We have now considered the elements of programming: We have used\nprimitive arithmetic operations, we have combined these operations, and\nwe have abstracted these composite operations by\ndeclaring them as compound functions.\n\nBut that is not enough to enable us to say that we know\nhow to program.\n\nOur situation is analogous to that of someone who has\nlearned the rules for how the pieces move in chess but knows nothing\nof typical openings, tactics, or strategy.\n\nLike the novice chess\nplayer, we don t yet know the common patterns of usage in the domain.\n\nWe lack the knowledge of which moves are worth making\n(which functions are worth declaring).\n\nWe lack the experience to predict the consequences of making a move\n(executing a function).\n\nThe ability to visualize the consequences of the actions under\nconsideration is crucial to becoming an expert programmer, just as it\nis in any synthetic, creative activity.\n\nIn becoming an expert\nphotographer, for example, one must learn how to look at a scene and\nknow how dark each region will appear on a print for each possible\nchoice of exposure and\nprocessing options.\nprocessing\nto obtain the desired effects.\n\nSo it is with programming, where we are\nplanning the course of action to be taken by a process and where we control\nthe process by means of a program.\n\nTo become experts, we must learn to\nvisualize the processes generated by various types of\nfunctions.\n\nOnly after we have developed such a skill can we learn\nto reliably construct programs that exhibit the desired behavior.\n\nA\nfunction\nis a\nlocal evolution of a\ncomputational process.\n\nIt specifies how each stage of the process is\nbuilt upon the previous stage.", + "token_count": 284, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Functions_and_the_Processes_They_Generate_1" + }, + { + "content": "It specifies how each stage of the process is\nbuilt upon the previous stage.\n\nWe would like to be able to make\nstatements about the overall, or global , behavior of a\nprocess whose local\nfunction.\n\nThis is very difficult to do in general, but we can at least try to\ndescribe some typical patterns of process evolution.\n\nIn this section we will examine some common shapes for\nprocesses generated by simple\nfunctions.\n\nWe will also investigate the rates at which these processes consume the\nimportant computational resources of time and space.\n\nThe\nfunctions\nwe will consider are very simple.\n\nTheir role is like that played by test\npatterns in photography: as oversimplified prototypical patterns, rather\nthan practical examples in their own right.", + "token_count": 123, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Functions_and_the_Processes_They_Generate_2" + }, + { + "content": "This section describes two methods for checking the primality of an\ninteger $n$ , one with order of growth\n$\\Theta(\\sqrt{n})$ , and a\nprobabilistic algorithm with order of growth\n$\\Theta(\\log n)$.\n\nThe exercises at the end of\nthis section suggest programming projects based on these algorithms.\n\nSince ancient times, mathematicians have been fascinated by problems\nconcerning prime numbers, and many people have worked on the problem\nof determining ways to test if numbers are prime.\n\nOne way\nto test if a number is prime is to find the number s divisors.\n\nThe\nfollowing program finds the smallest integral divisor (greater than 1)\nof a given number $n$.\n\nIt does this in a\nstraightforward way, by testing $n$ for\ndivisibility by successive integers starting with 2.\n\n```javascript\nsmallest_divisor_definition\n square_definition\n smallest_divisor_example\n\nfunction smallest_divisor(n) {\n return find_divisor(n, 2);\n}\nfunction find_divisor(n, test_divisor) {\n return square(test_divisor) > n\n ? n\n : divides(test_divisor, n)\n ? test_divisor\n : find_divisor(n, test_divisor + 1);\n}\nfunction divides(a, b) {\n return b % a === 0;\n}\n```\n\n```javascript\nsmallest_divisor_example\n smallest_divisor_definition\n 2\n\nsmallest_divisor(42);\n```\n\nWe can test whether a number is prime as follows: $n$ is prime if and only if $n$ is its own smallest divisor.\n\n```javascript\nprime_definition\n smallest_divisor_definition\n prime_example\n\nfunction is_prime(n) {\n return n === smallest_divisor(n);\n}\n```\n\n```javascript\nprime_example\n prime_definition\n false\n\nis_prime(42);\n```\n\nThe end test for\nfind_divisor\nis based on the fact that if $n$ is not prime it\nmust have a divisor less than or equal to\n$\\sqrt{n}$. $\\sqrt{n}$.\n\nConsequently, the number of steps\nrequired to identify $n$ as prime will have order\nof growth $\\Theta(\\sqrt{n})$.\n\nThe $\\Theta(\\log n)$ primality test is based on\na result from number theory known as\ns Little\nTheorem.", + "token_count": 280, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Example: Testing for Primality", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Example_Testing_for_Primality_1" + }, + { + "content": "The $\\Theta(\\log n)$ primality test is based on\na result from number theory known as\ns Little\nTheorem.\n\nFermat s Little Theorem:\n$n$ is a prime number and\n$a$ is any positive integer less than\n$n$ , then $a$ raised\nto the $n$ th power is congruent to\n$a$ modulo $n$.\n\n(Two numbers are said to be\ncongruent modulo\n$n$ if they both have the same remainder when\ndivided by $n$.\n\nThe remainder of a number\n$a$ when divided by\n$n$ is also referred to as the\nremainder of $a$ modulo\n$n$ , or simply as $a$\nmodulo $n$.)\n\nIf $n$ is not prime, then, in general, most of\nthe numbers $a < n$ will not satisfy the above\nrelation.\n\nThis leads to the following algorithm for testing primality:\nGiven a number $n$ , pick a\n$a < n$ and compute the\nremainder of $a^n$ modulo\n$n$.\n\nIf the result is not equal to\n$a$ , then $n$ is\ncertainly not prime.\n\nIf it is $a$ , then chances\nare good that $n$ is prime.\n\nNow pick another\nrandom number $a$ and test it with the same\nmethod.\n\nIf it also satisfies the equation, then we can be even more\nconfident that $n$ is prime.\n\nBy trying more and\nmore values of $a$ , we can increase our\nconfidence in the result.\n\nThis algorithm is known as the Fermat test.\n\nTo implement the Fermat test, we need a function that computes the\n\n```javascript\nexpmod_definition\n expmod_example\n even_definition\n square_definition\n\nfunction expmod(base, exp, m) {\n return exp === 0\n ? 1\n : is_even(exp)\n ? square(expmod(base, exp / 2, m)) % m\n : (base * expmod(base, exp - 1, m)) % m;\n}\n```\n\n```javascript\nexpmod_example\n expmod_definition\n 4\n\nexpmod(4, 3, 5);\n```\n\nThis is very similar to the\nfast_expt\nfunction\nof section.", + "token_count": 298, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Example: Testing for Primality", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Example_Testing_for_Primality_2" + }, + { + "content": "This is very similar to the\nfast_expt\nfunction\nof section.\n\nIt uses successive\nsquaring, so that the number of steps grows logarithmically with the\nexponent.\n\nThe Fermat test is performed by choosing at random a number\n$a$ between 1 and\n$n-1$ inclusive and checking whether the remainder\nmodulo $n$ of the\n$n$ th power of $a$ is\nequal to $a$.\n\nThe random number\n$a$ is chosen using the\n\n```javascript\nprimitive function\n\tmath_random,\n```\n\n```javascript\n$n-1$, we multiply\n\tthe return value of math_random by\n\t$n-1$, round down the result with the\n\tprimitive function\n\tmath_floor,\n\tand add 1:\n```\n\n```javascript\nrandom_definition\n\nfunction random(n) {\n return math_floor(math_random() * n);\n}\n```\n\n```javascript\nfermat_test_definition\n square_definition\n expmod_definition\n random_definition\n fermat_test_example\n\nfunction fermat_test(n) {\n function try_it(a) {\n return expmod(a, n, n) === a;\n }\n return try_it(1 + math_floor(math_random() * (n - 1)));\n}\n```\n\n```javascript\nfermat_test_example\n fermat_test_definition\n true\n\nfermat_test(97);\n```\n\nThe following\nfunction\nruns the test a given number of times, as specified by a parameter.\n\nIts\nvalue is true if the test succeeds every time, and false otherwise.\n\n```javascript\nfast_prime_definition\n square_definition\n expmod_definition\n random_definition\n fermat_test_definition\n fast_prime_example\n\nfunction fast_is_prime(n, times) {\n return times === 0\n ? true\n : fermat_test(n)\n ? fast_is_prime(n, times - 1)\n : false;\n}\n```\n\n```javascript\nfast_prime_example\n fast_prime_definition\n true\n\nfast_is_prime(97, 3);\n```\n\nThe Fermat test differs in character from most familiar algorithms, in which\none computes an answer that is guaranteed to be correct.\n\nHere, the answer\nobtained is only probably correct.\n\nMore precisely, if\n$n$ ever fails the Fermat test, we can be certain\nthat $n$ is not prime.\n\nBut the fact that\n$n$ passes the test, while an extremely strong\nindication, is still not a guarantee that $n$ is\nprime.", + "token_count": 277, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Example: Testing for Primality", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Functions_Example_Testing_for_Primality_3" + }, + { + "content": "But the fact that\n$n$ passes the test, while an extremely strong\nindication, is still not a guarantee that $n$ is\nprime.\n\nWhat we would like to say is that for any number\n$n$ , if we perform the test enough times and find\nthat $n$ always passes the test, then the\nprobability of error in our primality test can be made as small as we like.\n\nUnfortunately, this assertion is not quite correct.\n\nThere do exist numbers\nthat fool the Fermat test: numbers $n$ that are\nnot prime and yet have the property that $a^n$ is\ncongruent to $a$ modulo\n$n$ for all integers\n$a < n$.\n\nSuch numbers are extremely rare, so\nthe Fermat test is quite reliable in practice. $n$ by choosing a random integer\n$a < n$ and checking some condition that\ndepends upon $n$ and\n$a$.\n\n(See\nexercise for an example of such a test.)\nOn the other hand, in contrast to the Fermat test, one can prove that, for\nany $n$ , the condition does not hold for most of\nthe integers $a < n$ unless\n$n$ is prime.\n\nThus, if\n$n$ passes the test for some random choice\nof $a$ , the chances are better than even\nthat $n$ is prime.\n\nIf\n$n$ passes the test for two random choices of\n$a$ , the chances are better than 3 out of 4 that\n$n$ is prime.\n\nBy running the test with more and\nmore randomly chosen values of $a$ we can make\nthe probability of error as small as we like.\n\nThe existence of tests for which one can prove that the chance of error\nbecomes arbitrarily small has sparked interest in algorithms of this type,\nwhich have come to be known as probabilistic algorithms.\n\nThere is", + "token_count": 294, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Example: Testing for Primality", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Functions_Example_Testing_for_Primality_4" + }, + { + "content": "Consider the problem of computing the exponential of a given number.\n\nWe would like a\nfunction\nthat takes as arguments a base $b$ and a positive\ninteger exponent $n$ and\ncomputes $b^n$.\n\nOne way to do this is via\nthe recursive definition\n\\[\n\\begin{array}{lll}\nb^{n} &=& b\\cdot b^{n-1}\\\\\nb^{0} &=& 1\n\\end{array}\n\\]\nwhich translates readily into the\nfunction\n\n```javascript\nexpt_definition\n expt_example\n\nfunction expt(b, n) {\n return n === 0\n ? 1\n : b * expt(b, n - 1);\n}\n```\n\n```javascript\nexpt_example\n expt_definition\n 81\n\nexpt(3, 4);\n```\n\nThis is a linear recursive process, which requires\n$\\Theta(n)$ steps and\n$\\Theta(n)$ space.\n\nJust as with factorial, we\ncan readily formulate an equivalent linear iteration:\n\n```javascript\nexpt_linear_definition\n expt_example2\n 81\n\nfunction expt(b, n) {\n return expt_iter(b, n, 1);\n}\nfunction expt_iter(b, counter, product) {\n return counter === 0\n ? product\n : expt_iter(b, counter - 1, b * product);\n}\n```\n\n```javascript\nexpt_example2\n\nexpt(3, 4);\n```\n\nThis version requires $\\Theta(n)$ steps and $\\Theta(1)$ space.\n\nWe can compute exponentials in fewer steps by using $b^8$ as \\[ \\begin{array}{l} b\\cdot(b\\cdot(b\\cdot(b\\cdot(b\\cdot(b\\cdot(b\\cdot b)))))) \\end{array} \\] we can compute it using three multiplications: \\[\n\n\\begin{array}{lll} b^{2} &= & b\\cdot b\\\\ b^{4} &= & b^{2}\\cdot b^{2}\\\\ b^{8} &= & b^{4}\\cdot b^{4} \\end{array} \\]\n\nThis method works fine for exponents that are powers of 2.\n\nWe can also take\nadvantage of successive squaring in computing exponentials in general if we\nuse the rule\n\\[\n\\begin{array}{llll}\nb^{n} &=& (b^{n/2})^{2} &\\qquad\\,\\mbox{if}\\ n\\ \\mbox{is even}\\\\\nb^{n} &=& b\\cdot b^{n-1} &\\qquad\\mbox{if}\\ n\\ \\mbox{is odd}\n\\end{array}\n\\]\nWe can express this method as a\nfunction:\n\n```javascript\nexpt_log_definition\n square_definition\n even_definition\n fast_expt_example\n\nfunction fast_expt(b, n) {\n return n === 0\n ? 1\n : is_even(n)\n ? square(fast_expt(b, n / 2))\n : b * fast_expt(b, n - 1);\n}\n```\n\n```javascript\nfast_expt_example\n expt_log_definition\n 81\n\nfast_expt(3, 4);\n```", + "token_count": 297, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Exponentiation", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Exponentiation_1" + }, + { + "content": "We can also take\nadvantage of successive squaring in computing exponentials in general if we\nuse the rule\n\\[\n\\begin{array}{llll}\nb^{n} &=& (b^{n/2})^{2} &\\qquad\\,\\mbox{if}\\ n\\ \\mbox{is even}\\\\\nb^{n} &=& b\\cdot b^{n-1} &\\qquad\\mbox{if}\\ n\\ \\mbox{is odd}\n\\end{array}\n\\]\nWe can express this method as a\nfunction:\n\nwhere the predicate to test whether an integer is even is defined in terms of the\n\n```javascript\n%,\n\twhich computes the remainder after integer division,\n```\n\nby\n\n```javascript\neven_definition\n even_example\n\nfunction is_even(n) {\n return n % 2 === 0;\n}\n```\n\n```javascript\neven_example\n even_definition\n\nis_even(7);\n```\n\nThe process evolved by\nfast_expt\n$n$ in both space and\nnumber of steps.\n\nTo see this, observe that computing\n$b^{2n}$ using\nfast_expt\nrequires only one more multiplication than computing\n$b^n$.\n\nThe size of the exponent we can compute\ntherefore doubles (approximately) with every new multiplication we are\nallowed.\n\nThus, the number of multiplications required for an exponent of\n$n$ grows about as fast as the logarithm of\n$n$ to the base 2.\n\nThe process has\n$\\Theta(\\log n)$ growth.\n\nThe difference between $\\Theta(\\log n)$ growth\nand $\\Theta(n)$ growth becomes striking as\n$n$ becomes large.\n\nFor example,\nfast_expt\nfor $n=1000$ requires only 14\nmultiplications. ), although, as is\noften the case with iterative algorithms, this is not written down so\nstraightforwardly as the recursive algorithm.", + "token_count": 214, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Exponentiation", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Exponentiation_2" + }, + { + "content": "The greatest common divisor (GCD) of two integers\n$a$ and $b$ is defined\nto be the largest integer that divides both $a$\nand $b$ with no remainder.\n\nFor example, the GCD\nof 16 and 28 is 4.\n\nIn chapter , when we investigate how to\nimplement rational-number arithmetic, we will need to be able to compute\nGCDs in order to reduce rational numbers to lowest terms.\n\n(To reduce a\nrational number to lowest terms, we must divide both the numerator and the\ndenominator by their GCD.\n\nFor example, 16/28 reduces to 4/7.) One way to\nfind the GCD of two integers is to factor them and search for common\nfactors, but there is a famous algorithm that is much more efficient.\n\n$r$ is the remainder when\n$a$ is divided by\n$b$ , then the common divisors of\n$a$ and $b$ are\nprecisely the same as the common divisors of $b$\nand $r$.\n\nThus, we can use the equation\n\\[\\begin{array}{lll}\n\\textrm{GCD} (a, b) &=& \\textrm{GCD}(b, r)\n\\end{array}\\]\nto successively reduce the problem of computing a GCD to the problem of\ncomputing the GCD of smaller and smaller pairs of integers.\n\nFor example,\n\\[\\begin{array}{lll}\n\\textrm{GCD}(206,40) & = & \\textrm{GCD}(40,6) \\\\\n& = & \\textrm{GCD}(6,4) \\\\\n& = & \\textrm{GCD}(4,2) \\\\\n& = & \\textrm{GCD}(2,0) \\\\\n& = & 2\n\\end{array}\\]\nreduces $\\textrm{GCD}(206, 40)$ to\n$\\textrm{GCD}(2, 0)$ , which is 2.\n\nIt is\npossible to show that starting with any two positive integers and\nperforming repeated reductions will always eventually produce a pair\nwhere the second number is 0.\n\nThen the GCD is the other\nnumber in the pair.\n\nThis method for computing the GCD is\nknown as Euclid s Algorithm.\n\nIt is easy to express Euclid s Algorithm as a function:", + "token_count": 289, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Greatest Common Divisors", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Greatest_Common_Divisors_1" + }, + { + "content": "It is easy to express Euclid s Algorithm as a function:\n\n```javascript\ngcd_definition\n gcd_example\n\nfunction gcd(a, b) {\n return b === 0 ? a : gcd(b, a % b);\n}\n```\n\n```javascript\ngcd_example\n gcd_definition\n 4\n\ngcd(20, 12);\n```\n\nThis generates an iterative process, whose number of steps grows as the logarithm of the numbers involved.\n\nThe fact that the number of steps required by Euclid s Algorithm has Lam s Theorem: s Algorithm requires $k$ steps to compute the GCD\n\nof some pair, then the smaller number in the pair must be greater than or equal to the $k$ th Fibonacci number.\n\nWe can use this theorem to get an order-of-growth estimate for Euclid s\nAlgorithm.\n\nLet $n$ be the smaller of the two\ninputs to the\nfunction.\n\nIf the process takes $k$ steps, then we must have\n$n\\geq {\\textrm{Fib}} (k)\\approx\\phi^k/\\sqrt{5}$.\n\nTherefore the number of steps $k$ grows as the\nlogarithm (to the base $\\phi$ ) of\n$n$.\n\nHence, the order of growth is\n$\\Theta(\\log n)$.", + "token_count": 166, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Greatest Common Divisors", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Greatest_Common_Divisors_2" + }, + { + "content": "We begin by considering the\n\\[\n\\begin{array}{lll}\nn! &=& n\\cdot(n-1)\\cdot(n-2)\\cdots3\\cdot2\\cdot1\n\\end{array}\n\\]\nThere are many ways to compute factorials.\n\nOne way is to make use of\nthe observation that $n!$ is equal to\n$n$ times $(n-1)!$ for\nany positive integer $n$ :\n\\[\n\\begin{array}{lll}\nn! &=& n\\cdot\\left[(n-1)\\cdot(n-2)\\cdots3\\cdot2\\cdot1\\right] \\quad = \\quad n \\cdot(n-1)!\n\\end{array}\n\\]\nThus, we can compute $n!$ by computing\n$(n-1)!$ and multiplying the\nresult by $n$.\n\nIf we add the stipulation that 1!\nis equal to 1,\nthis observation translates directly into a\ncomputer function:\n\n```javascript\nfactorial_definition\n factorial_example\n 120\n\nfunction factorial(n) {\n return n === 1\n ? 1\n : n * factorial(n - 1);\n}\n```\n\n```javascript\nfactorial_example\n\nfactorial(5);\n```\n\n```javascript\nWe can use the substitution model of\n section to watch this\n .\n```\n\nNow let s take a different perspective on computing factorials.\n\nWe\ncould describe a rule for computing $n!$ by\nspecifying that we first multiply 1 by 2, then multiply the result by 3,\nthen by 4, and so on until we reach $n$.\n\nMore formally, we maintain a running product, together with a counter\nthat counts from 1 up to $n$.\n\nWe can describe\nthe computation by saying that the counter and the product simultaneously\nchange from one step to the next according to the rule\n\\[\n\\begin{array}{lll}\n\\textrm{product} & \\leftarrow & \\textrm{counter} \\cdot \\textrm{product}\\\\\n\\textrm{counter} & \\leftarrow & \\textrm{counter} + 1\n\\end{array}\n\\]\nand stipulating that $n!$ is the value of the\nproduct when the counter exceeds $n$.\n\nOnce again, we can recast our description as a function for computing factorials:\n\n```javascript\nfactorial_iterative_definition\n factorial_example\n\nfunction factorial(n) {\n return fact_iter(1, 1, n);\n}\nfunction fact_iter(product, counter, max_count) {\n return counter > max_count\n ? product\n : fact_iter(counter * product,\n counter + 1,\n max_count);\n}\n```\n\nAs before, we can use the substitution model to visualize the process", + "token_count": 302, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Linear Recursion and Iteration", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Linear_Recursion_and_Iteration_1" + }, + { + "content": "As before, we can use the substitution model to visualize the process\n\n```javascript\nA linear recursive process for computing 6!.\n\n\tA linear iterative process for computing\n\t$6!$.\n\n of computing $6!$, as shown in\n\tfigure.\n```\n\nCompare the two processes.\n\nFrom one point of view, they seem hardly\ndifferent at all.\n\nBoth compute the same mathematical function on the\nsame domain, and each requires a number of steps proportional to\n$n$\nto compute $n!$.\n\nIndeed, both processes even\ncarry out the same sequence of multiplications, obtaining the same sequence\nof partial products.\n\nOn the other hand, when we consider the\nshapes of the two processes, we find that they evolve quite\ndifferently.\n\nConsider the first process.\n\nThe substitution model reveals a shape of\nexpansion followed by contraction, indicated by the arrow in\nfigure.\n\nThe expansion occurs as the process builds up a chain of\ndeferred operations (in this case, a chain of multiplications).\n\nThe contraction occurs as the operations are actually performed.\n\nThis\ntype of process, characterized by a chain of deferred operations, is called a\nrecursive process.\n\nCarrying out this process requires that the\ninterpreter keep track of the operations to be performed later on.\n\nIn the\ncomputation of $n!$ , the length of the chain of\ndeferred multiplications, and hence the amount of information needed to\nkeep track of it,\n$n$ (is proportional to\n$n$ ), just like the number of steps.\n\nSuch a process is called a\nlinear recursive process.\n\nBy contrast, the second process does not grow and shrink.\n\nAt each\nstep, all we need to keep track of, for any $n$ ,\nare the current values of the\nnames\nmax_count.\n\nWe call this an\niterative process.", + "token_count": 280, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Linear Recursion and Iteration", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Linear_Recursion_and_Iteration_2" + }, + { + "content": "We call this an\niterative process.\n\nIn general, an iterative process is one whose\nstate can be summarized by a fixed number of\nstate variables , together with a fixed rule that describes how\nthe state variables should be updated as the process moves from state to\nstate and an (optional) end test that specifies conditions under which the\nprocess should terminate.\n\nIn computing $n!$ , the\nnumber of steps required grows linearly with $n$.\n\nSuch a process is called a\nlinear iterative process.\n\nThe contrast between the two processes can be seen in another way.\n\nIn the iterative case, the state variables provide a complete description of\nthe state of the process at any point.\n\nIf we stopped the computation between\nsteps, all we would need to do to resume the computation is to supply the\ninterpreter with the values of the three state variables.\n\nNot so with the\nrecursive process.\n\nIn this case there is some additional\nhidden information, maintained by the interpreter and not\ncontained in the state variables, which indicates where the process\nis in negotiating the chain of deferred operations.\n\nThe longer the\nchain, the more information must be maintained.\n\nIn contrasting iteration and recursion, we must be careful not to\nconfuse the notion of a\nprocess with the notion of a recursive\nfunction.\n\nWhen we describe a\nfunction\nas recursive, we are referring to the syntactic fact that the\nfunction declaration\nrefers (either directly or indirectly) to the\nfunction\nitself.\n\nBut when we describe a process as following a pattern that is, say,\nlinearly recursive, we are speaking about how the process evolves, not\nabout the syntax of how a\nfunction\nis written.\n\nIt may seem disturbing that we refer to a recursive\nfunction\nsuch as\nfact_iter\nas generating an iterative process.", + "token_count": 298, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Linear Recursion and Iteration", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Functions_Linear_Recursion_and_Iteration_3" + }, + { + "content": "It may seem disturbing that we refer to a recursive\nfunction\nsuch as\nfact_iter\nas generating an iterative process.\n\nHowever, the process really is\niterative: Its state is captured completely by its three state variables,\nand an interpreter need keep track of only three\nnames\nin order to execute the process.\n\nOne reason that the distinction between process and\nfunction\nmay be confusing is that most implementations of common languages\n(including\nare designed in such a way that the interpretation of\nany recursive\nfunction\nconsumes an amount of memory that grows with the number of\nfunction\ncalls, even when the process described is, in principle, iterative.\n\nAs a consequence, these languages can describe iterative processes only\nby resorting to special-purpose\nlooping constructs such as\n$\\texttt{do}$ ,\n$\\texttt{repeat}$ ,\n$\\texttt{until}$ ,\n$\\texttt{for}$ , and\n$\\texttt{while}$.\n\nThe implementation of\nJavaScript\nwe shall consider in chapter does not share this defect.\n\nIt will\nexecute an iterative process in constant space, even if the iterative\nprocess is described by a recursive\nfunction.\n\n```javascript\nAn implementation with this property is called\n\ttail-recursive.\n```", + "token_count": 178, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Linear Recursion and Iteration", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Functions_Linear_Recursion_and_Iteration_4" + }, + { + "content": "The previous examples illustrate that processes can differ\nconsiderably in the rates at which they consume computational\nresources.\n\nOne convenient way to describe this difference is to use\nthe notion of\norder of growth to obtain a gross measure of the\n\nLet $n$ be a parameter that measures the size of\nthe problem, and let $R(n)$ be the amount\nof resources the process requires for a problem of size\n$n$.\n\nIn our previous examples we took\n$n$ to be the number for which a given\nfunction is to be computed, but there are other possibilities.\n\nFor instance, if our goal is to compute an approximation to the\nsquare root of a number, we might take\n$n$ to be the number of digits accuracy required.\n\nFor matrix multiplication we might take $n$ to\nbe the number of rows in the matrices.\n\nIn general there are a number of\nproperties of the problem with respect to which it will be desirable to\nanalyze a given process.\n\nSimilarly, $R(n)$\nmight measure the number of internal storage registers used, the\nnumber of elementary machine operations performed, and so on.\n\nIn\ncomputers that do only a fixed number of operations at a time, the\ntime required will be proportional to the number of elementary machine\noperations performed.\n\nWe say that $R(n)$ has order of growth\n$\\Theta(f(n))$ , written\n$R(n)=\\Theta(f(n))$ (pronounced\ntheta of $f(n)$ ), if there are\npositive constants $k_1$ and\n$k_2$ independent of\n$n$ such that\n\\[\n\\begin{array}{lllll}\nk_1\\,f(n) & \\leq & R(n) & \\leq & k_2\\,f(n)\n\\end{array}\n\\]\nfor any sufficiently large value of $n$.\n\n(In other words, for large $n$ ,\nthe value $R(n)$ is sandwiched between\n$k_1f(n)$ and\n$k_2f(n)$.)\n\nthe\nnumber of steps grows proportionally to the input\n$n$.\n\nThus, the steps required for this process\ngrows as $\\Theta(n)$.", + "token_count": 298, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Orders of Growth", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Orders_of_Growth_1" + }, + { + "content": "Thus, the steps required for this process\ngrows as $\\Theta(n)$.\n\nWe also saw that the space\nrequired grows as $\\Theta(n)$.\n\nFor the\n$\\Theta(n)$ but the space is\n$\\Theta(1)$ that is,\nconstant. $\\Theta(\\phi^{n})$ steps and space\n$\\Theta(n)$ , where\n$\\phi$ is the golden ratio described in\nsection.\n\nOrders of growth provide only a crude description of the behavior of a\nprocess.\n\nFor example, a process requiring $n^2$\nsteps and a process requiring $1000n^2$ steps and\na process requiring $3n^2+10n+17$ steps all have\n$\\Theta(n^2)$ order of growth.\n\nOn the other hand,\norder of growth provides a useful indication of how we may expect the\nbehavior of the process to change as we change the size of the problem.\n\nFor a\n$\\Theta(n)$ (linear) process, doubling the size\nwill roughly double the amount of resources used.\n\nFor an\nwe will examine two algorithms whose order of growth is\n\n-\n- The function p\nwill call itself recursively as long as the angle value is greater\nthan 0.1.\n\nThere will be altogether 5 calls of\np , with arguments 12.15, 4.05,\n1.35, 0.45, 0.15 and 0.05.\n-\n-\nThe function sine gives\nrise to a recursive process.\n\nIn each recursive call, the\nangle is divided by 3\nuntil its absolute value is smaller than 0.1.\n\nThus the number of steps and the space required has an order\nof growth of $O(\\log a)$.\n\nNote that the base of the logarithm\nis immaterial for the order of growth because the logarithms\nof different bases differ only by a constant factor.", + "token_count": 253, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Orders of Growth", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Orders_of_Growth_2" + }, + { + "content": "Another common pattern of computation is called tree recursion.\n\nAs an example, consider computing the sequence of\n\\[\\begin{array}{l}\n0, 1, 1, 2, 3, 5, 8, 13, 21, \\ldots\n\\end{array}\\]\nIn general, the Fibonacci numbers can be defined by the rule\n\\[\\begin{array}{lll}\n\\textrm{Fib}(n) & = & \\left\\{ \\begin{array}{ll}\n0 & \\mbox{if $n=0$}\\\\\n1 & \\mbox{if $n=1$}\\\\\n\\textrm{Fib}(n-1)+\\textrm{Fib}(n-2) & \\mbox{otherwise}\n\\end{array}\n\\right.\n\\end{array}\\]\nWe can immediately translate this definition into a recursive\nfunction\nfor computing Fibonacci numbers:\n\n```javascript\nfib_definition\n fib_example\n\nfunction fib(n) {\n return n === 0\n ? 0\n : n === 1\n ? 1\n : fib(n - 1) + fib(n - 2);\n}\n```\n\n```javascript\nfib_example\n fib_definition\n 8\n\nfib(6);\n```\n\n```javascript\nThe tree-recursive process generated in computing\n\t fib(5).\n```\n\nConsider the pattern of this computation.\n\nTo compute\nfib(5),\nwe compute\nfib(4)\nand\nfib(3).\n\nTo compute\nfib(4),\nwe compute\nfib(3)\nand\nfib(2).\n\nIn general, the evolved process looks like a tree, as shown in\nfigure.\n\nNotice that the branches split into\ntwo at each level (except at the bottom); this reflects the fact that the\nfunction\ncalls itself twice each time it is invoked.\n\nThis\nfunction\nis instructive as a prototypical tree recursion, but it is a terrible way to\ncompute Fibonacci numbers because it does so much redundant computation.\n\nNotice in\nfigure\nthat the entire\ncomputation of\n\n```javascript\nfib(3)almost\n\thalf the workis\n```\n\nduplicated.\n\nIn fact, it is not hard to show that the number of times the\nfunction\nwill compute\nfib(1)\nor\nfib(0)\n(the number of leaves in the above tree, in general) is precisely\n$\\textrm{Fib}(n+1)$.\n\nTo get an idea of how\nbad this is, one can show that the value of\n$\\textrm{Fib}(n)$\n$n$.", + "token_count": 274, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Tree Recursion", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Tree_Recursion_1" + }, + { + "content": "To get an idea of how\nbad this is, one can show that the value of\n$\\textrm{Fib}(n)$\n$n$.\n\nMore precisely\n(see exercise ),\n$\\textrm{Fib}(n)$ is the closest integer to\n$\\phi^{n} /\\sqrt{5}$ , where\n\\[\\begin{array}{lllll}\n\\phi&=&(1+\\sqrt{5})/2 & \\approx & 1.6180\n\\end{array}\\]\nis the\ngolden ratio , which satisfies the equation\n\\[\\begin{array}{lll}\n\\phi^{2} &=&\\phi + 1\n\\end{array}\\]\nThus, the process uses a number of steps that grows exponentially with the\ninput.\n\nOn the other hand, the space required grows only linearly with the\ninput, because we need keep track only of which nodes are above us in the\ntree at any point in the computation.\n\nIn general, the number of steps\nrequired by a tree-recursive process will be proportional to the number of\nnodes in the tree, while the space required will be proportional to the\nmaximum depth of the tree.\n\nWe can also formulate an iterative process for computing the Fibonacci\nnumbers.\n\nThe idea is to use a pair of integers $a$\nand $b$ , initialized to\n$\\textrm{Fib}(1)=1$ and\n$\\textrm{Fib}(0)=0$ , and to repeatedly apply the\nsimultaneous transformations\n\\[\\begin{array}{lll}\na & \\leftarrow & a+b \\\\\nb & \\leftarrow & a\n\\end{array}\\]\nIt is not hard to show that, after applying this transformation\n$n$ times, $a$ and\n$b$ will be equal, respectively, to\n$\\textrm{Fib}(n+1)$ and\n$\\textrm{Fib}(n)$.\n\nThus, we can compute\nFibonacci numbers iteratively using the\nfunction\n\n```javascript\nfib_example\n 8\n\nfunction fib(n) {\n return fib_iter(1, 0, n);\n}\nfunction fib_iter(a, b, count) {\n return count === 0\n ? b\n : fib_iter(a + b, a, count - 1);\n}\n```\n\nThis second method for computing $\\textrm{Fib}(n)$\nis a linear iteration.\n\nThe difference in number of steps required by the two\nmethods one linear in $n$ , one growing as\nfast as $\\textrm{Fib}(n)$ itself is\nenormous, even for small inputs.", + "token_count": 295, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Tree Recursion", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Tree_Recursion_2" + }, + { + "content": "The difference in number of steps required by the two\nmethods one linear in $n$ , one growing as\nfast as $\\textrm{Fib}(n)$ itself is\nenormous, even for small inputs.\n\nOne should not conclude from this that tree-recursive processes are useless.\n\nWhen we consider processes that operate on hierarchically structured data\nrather than numbers, we will find that tree recursion is a natural and\npowerful tool.\nfunction\nis much less efficient than the second one, it is more straightforward,\nbeing little more than a translation into\nJavaScript\nof the definition of the Fibonacci sequence.\n\nTo formulate the iterative\nalgorithm required noticing that the computation could be recast as an\niteration with three state variables.\n\nIt takes only a bit of cleverness to come up with the iterative Fibonacci\nalgorithm.\n\nIn contrast, consider the following problem:\nHow many different ways can we make change of\n1.00 (100 cents),\ngiven half-dollars, quarters, dimes, nickels, and pennies\n(50 cents, 25 cents, 10 cents, 5 cents, and 1 cent, respectively)?\n\nMore generally, can\nwe write a\nfunction\nto compute the number of ways to change any given amount of money?\n\nThis problem has a simple solution as a recursive\nfunction.\n\nSuppose we think of the types of coins available as arranged in some order.\n\nThen the following relation holds:\nThe number of ways to change amount $a$ using\n$n$ kinds of coins equals\n-\n-\nthe number of ways to change amount $a$\nusing all but the first kind of coin, plus\n-\n-\nthe number of ways to change amount $a-d$\nusing all $n$ kinds of coins, where\n$d$ is the denomination of the first kind\nof coin.", + "token_count": 274, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Tree Recursion", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Functions_Tree_Recursion_3" + }, + { + "content": "Then the following relation holds:\nThe number of ways to change amount $a$ using\n$n$ kinds of coins equals\n-\n-\nthe number of ways to change amount $a$\nusing all but the first kind of coin, plus\n-\n-\nthe number of ways to change amount $a-d$\nusing all $n$ kinds of coins, where\n$d$ is the denomination of the first kind\nof coin.\n\nTo see why this is true, observe that the ways to make change can be divided\ninto two groups: those that do not use any of the first kind of coin, and\nthose that do.\n\nTherefore, the total number of ways to make change for some\namount is equal to the number of ways to make change for the amount without\nusing any of the first kind of coin, plus the number of ways to make change\nassuming that we do use the first kind of coin.\n\nBut the latter number is\nequal to the number of ways to make change for the amount that remains after\nusing a coin of the first kind.\n\nThus, we can recursively reduce the problem of changing a given amount to\nproblems of changing smaller amounts or using fewer kinds of coins.\n\nConsider\nthis reduction rule carefully, and convince yourself that we can use it to\ndescribe an algorithm if we specify the following degenerate\ncases:\n-\n-\nIf $a$ is exactly 0, we should count that\nas 1 way to make change.\n-\n-\nIf $a$ is less than 0, we should count\nthat as 0 ways to make change.\n-\n- If $n$ is 0, we should count that\nas 0 ways to make change.\n\nWe can easily translate this description into a recursive\nfunction:", + "token_count": 288, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Tree Recursion", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Functions_Tree_Recursion_4" + }, + { + "content": "We can easily translate this description into a recursive\nfunction:\n\n```javascript\ncount_change_definition\n count_change_example\n\nfunction count_change(amount) {\n return cc(amount, 5);\n}\n\nfunction cc(amount, kinds_of_coins) {\n return amount === 0\n ? 1\n : amount < 0 || kinds_of_coins === 0\n ? 0\n : cc(amount, kinds_of_coins - 1)\n +\n cc(amount - first_denomination(kinds_of_coins),\n kinds_of_coins);\n}\n\nfunction first_denomination(kinds_of_coins) {\n return kinds_of_coins === 1 ? 1\n : kinds_of_coins === 2 ? 5\n : kinds_of_coins === 3 ? 10\n : kinds_of_coins === 4 ? 25\n : kinds_of_coins === 5 ? 50\n : 0;\n}\n```\n\n(The\nfirst_denomination function\ntakes as input the number of kinds of coins available and returns the\ndenomination of the first kind.\n\nHere we are thinking of the coins as\narranged in order from largest to smallest, but any order would do as well.)\nWe can now answer our original question about changing a dollar:\n\n```javascript\ncount_change_example\n count_change_definition\n 292\n\ncount_change(100);\n```\n\nThe function count_change\ngenerates a tree-recursive process with redundancies similar to those in\nour first implementation of\nOn the other hand, it is not\nobvious how to design a better algorithm for computing the result, and we\nleave this problem as a challenge.\n\nThe observation that a\nsmart compiler that could transform\ntree-recursive\nfunctions\ninto more efficient\nfunctions\nthat compute the same result.", + "token_count": 211, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Tree Recursion", + "chunk_index": 5, + "chunk_id": "Building_Abstractions_with_Functions_Tree_Recursion_5" + }, + { + "content": "A powerful programming language is more than just a means for\ninstructing a computer to perform tasks.\n\nThe language also serves as\na framework within which we organize our ideas about processes.\n\nThus,\nwhen we describe a language, we should pay particular attention to the\nmeans that the language provides for combining simple ideas to form\nmore complex ideas.\n\nEvery powerful language has three mechanisms for\naccomplishing this:\n-\n-\nprimitive expressions ,\ncerned with,\n-\n-\nmeans of combination , by\n-\n-\nmeans of abstraction ,\n\nIn programming, we deal with two kinds of elements:\nand\nstuff that we want to manipulate, and\nfunctions\nare descriptions of the rules for manipulating the data.\n\nThus, any powerful programming language should be able to describe\nprimitive data and primitive\nfunctions\nand should have methods for\ncombining and abstracting\nfunctions\nand data.\n\nIn this chapter we will deal only with simple functions. functions to manipulate compound data as well.", + "token_count": 158, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_The_Elements_of_Programming_1" + }, + { + "content": "We have identified in\nJavaScript\nsome of the elements that must appear in any powerful programming language:\n-\n-\nNumbers and arithmetic operations are primitive data and\nfunctions.\n-\n-\nNesting of combinations provides a means of combining operations.\n-\n-\nConstant declarations that associate names with values provide a\nlimited means of abstraction.\n\nNow we will learn about\nfunction declarations,\na much more powerful abstraction technique by which a compound\noperation can be given a name and then referred to as a unit.\n\nWe begin by examining how to express the idea of\nsquaring.\n\nWe might say,\nTo square something, take it times itself.\n\n```javascript\nsquare_definition\n square_example\n\nfunction square(x) {\n return x * x;\n}\n```\n\n```javascript\nsquare_example\n\nsquare(14);\n```\n\nWe can understand this in the following way:\n\\begin{flushleft}\\normalcodesize\n\\begin{tabular}{@{}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c}\n\\tt\\textbf{function} & \\tt square( & \\tt x & \\tt ) \\verb+{+ & \\tt\\textbf{return} & \\tt x & \\tt * & \\tt x & \\tt; \\verb+}+ \\\\\n$\\Big\\uparrow$ & $\\Big\\uparrow$ & $\\Big\\uparrow$ & & & $\\Big\\uparrow$ & $\\Big\\uparrow$ & $\\Big\\uparrow$ \\\\[4pt]\n\\normalsize To & \\normalsize square & \\normalsize something, & & \\normalsize take &\\normalsize it & \\normalsize times & \\normalsize itself. \\\\\n\\end{tabular}\n\\end{flushleft}\nWe have here a\ncompound function,\nwhich has been given the name\nfunction\nrepresents the operation of multiplying something by itself.\n\nThe thing to\nbe multiplied is given a local name,\nEvaluating the\ndeclaration\ncreates this compound\nfunction\nand associates it with the name\n\nThe simplest form of a function declaration is\n\n```javascript\nfunction name(parameters) { return expression; }\n```\n\nThe name is a symbol to be associated with the function definition in the environment. parameters are the names used within the body of the\n\nfunction to refer to the corresponding arguments of the function.", + "token_count": 289, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Compound Functions", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Compound_Functions_1" + }, + { + "content": "function to refer to the corresponding arguments of the function.\n\n```javascript\nThe parameters\n\tare grouped within\n\tbody of a function declaration is a single\n\treturn statement,return\n\tfollowed by the return expression\n\tthat will yield the value of the function application, when the\n\n\tparameters are replaced by the actual arguments to which the function\n\tis applied. Like constant declarations and expression statements,\n\treturn statements\n```\n\n```javascript\nHaving declared square,\n\twe can now use it in a\n\tfunction application expression, which we turn into a statement\n\tusing a semicolon:\n```\n\n```javascript\nsquare_definition\n\nsquare(21);\n```\n\n```javascript\nafter operator\n\tcombinationsthe second kind of combination of\n\texpressions into larger expressions that we encounter.\n\tThe general form of a function application is\n\nfunction-expression(argument-expressions)\n\n\twhere the\n\tfunction-expression\n\tof the application specifies\n\tthe function to be applied to the comma-separated\n argument-expressions.\n\tTo evaluate a function application, the interpreter follows\n\t.\n\n\t To evaluate a function application, do the following:\n\n\t Evaluate the subexpressions of the application, namely\n\t the function expression and the argument expressions.\n\n\t Apply the function that is the value of the function expression\n to the values of the argument expressions.\n```\n\n```javascript\nsquare_definition\n\nsquare(2 + 5);\n```\n\n```javascript\nHere, the argument expression is itself a compound expression,\n\tthe operator combination 2 + 5.\n```\n\n```javascript\nsquare_square\n 81\n square_definition\n\nsquare(square(3));\n```\n\nOf course function application expressions can also serve as argument expressions.\n\nWe can also use\nfunctions.\n\nFor example, $x^2 +y^2$ can be expressed as\n\n```javascript\nsquare(x) + square(y)\n```\n\nWe can easily declare a\n\n```javascript\nfunction\n\tsum_of_squares\n```\n\nthat, given any two numbers as arguments, produces the sum of their squares:\n\n```javascript\nsum_of_squares\n 25\n sum_of_squares_example\n square_definition\n\nfunction sum_of_squares(x, y) {\n return square(x) + square(y);\n}\n```\n\n```javascript\nsum_of_squares_example\n 25\n sum_of_squares\n\nsum_of_squares(3, 4);\n```\n\nNow we can use sum_of_squares as a building block in constructing further functions:\n\n```javascript\nf\n f_example\n 136\n sum_of_squares\n\nfunction f(a) {\n return sum_of_squares(a + 1, a * 2);\n}\n```\n\n```javascript\nf_example\n f\n\nf(5);\n```", + "token_count": 320, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Compound Functions", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Compound_Functions_2" + }, + { + "content": "Now we can use sum_of_squares as a building block in constructing further functions:\n\n```javascript\nIn addition to compound functions, any JavaScript environment provides\n\tmath_log,\n\twhich computes the natural logarithm of its argument.math_log(1) results in the\tnumber 0.\n\tIndeed, one could not tell by looking at the definition of\n\tsum_of_squares given above whether\n\tsquare was built into the\n\tinterpreter, loaded from a library, or defined as a compound function.\n```", + "token_count": 69, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Compound Functions", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Functions_Compound_Functions_3" + }, + { + "content": "The expressive power of the class of functions that we can define at this point is very limited, because we have no way to make\n\ntests and to perform different operations depending on the result of a test.\n\n```javascript\n\\[\\begin{array}{lll}\n |x| & = & \\left\\{ \\begin{array}{rl}\n x & \\mbox{if $x \\geq 0$} \\\\\n -x & \\mbox{otherwise}\n \\end{array}\n \\right.\n \\end{array}\\]\n\n\tcase analysis and can be written\n\tin JavaScript using a conditional expression as\n\n\t abs_definition\n abs_example\n\nfunction abs(x) {\n return x >= 0 ? x : - x;\n}\n\n abs_example\n\t abs_definition\n\t 5\n\nabs(-5);\n\n\twhich could be expressed in English as If $x$ is\n\tgreater than or equal to zero, return$x$; otherwise\n\treturn $- x$.\n The general form of a conditional expression is\n\npredicate ? consequent-expression : alternative-expression\n\n Conditional\n\tpredicatethat is,\n an expression whose value is either\n\ttrue or false, two distinguished\n\tboolean values in JavaScript.\n\tThe primitive boolean expressions\n\ttrue and\n\tfalse trivially evaluate\n\tto the boolean values true and false, respectively.\n\tThe predicate\n is followed by a question mark, the\n consequent-expression,\n a colon, and finally the\n alternative-expression.\n```\n\nTo\npredicate\nof the expression.\n\nIf the\npredicate\nevaluates to true, the interpreter evaluates the\nconsequent-expression and returns its value as the value of the conditional.\n\nIf the predicate\nevaluates to false, it evaluates the\nalternative-expression and returns its value as the value of the\nconditional.\n\nThe word\npredicate is used for operators and functions that\nreturn true or false, as well as for expressions that\nevaluate to true or false.\n\nThe absolute-value function\nabs makes use of the\n>= ,\nan operator that takes two numbers as arguments and tests whether the\nfirst number is greater than or equal to the second number, returning\ntrue or false accordingly.", + "token_count": 286, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Conditional Expressions and Predicates", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Conditional_Expressions_and_Predicates_1" + }, + { + "content": "The absolute-value function\nabs makes use of the\n>= ,\nan operator that takes two numbers as arguments and tests whether the\nfirst number is greater than or equal to the second number, returning\ntrue or false accordingly.\n\nIf we prefer to handle the zero case separately, we can specify the function that computes the absolute value of a number by writing \\[\\begin{array}{lll}\n\n|x| &=& \\left\\{ \\begin{array}{rl} x & \\mbox{if $x > 0$} \\\\ 0 & \\mbox{if $x = 0$} \\\\ -x & \\mbox{otherwise} \\end{array} \\right. \\end{array}\\] In\n\nJavaScript, we express a case analysis with multiple cases by nesting conditional expressions as alternative expressions inside other conditional expressions:\n\n```javascript\nabs_example\n\nfunction abs(x) {\n return x > 0\n ? x\n : x === 0\n ? 0\n : - x;\n}\n```\n\nParentheses are not needed around the alternative expression\nx === 0 ?\n\n0 : - x , because\nthe conditional-expression syntactic form\n? s\nand : s under the first predicate\nof the case analysis.\n\nThe general form of a\n\n```javascript\np$_1$\n? e$_1$\n: p$_2$\n? e$_2$\n$\\vdots$\n: p$_n$\n? e$_n$\n: final-alternative-expression\n```\n\nWe call a predicate $p_i$\ntogether with its consequent expression\n$e_i$\na\nclause.\n\nA case analysis\ncan be seen as a sequence of clauses, followed by a final\nalternative expression.\np $_1$.\n\nIf its value is false, then p $_2$\nis evaluated.\n\nIf p $_2$ s\nvalue is also false, then p $_3$\nis evaluated.\n\nThis process continues until a predicate is\nfound whose value is true, in which case the interpreter returns the\nvalue of the corresponding\ne\nof the clause\nas the value of the case analysis.\n\nIf none of the\np s\nis found to be true, the value of the case analysis\nis the value of the final alternative expression.", + "token_count": 300, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Conditional Expressions and Predicates", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Conditional_Expressions_and_Predicates_2" + }, + { + "content": "If none of the\np s\nis found to be true, the value of the case analysis\nis the value of the final alternative expression.\n\nIn addition to primitive predicates such as\n>= ,\n> ,\n< ,\n<= ,\n=== , and\n!== that are applied to\nnumbers,\n-\n-\nexpression $_1$ &&\nexpression $_2$\nThis operation expresses\nlogical conjunction , meaning roughly\nthe same as the English word and.\n\nWe assume\nexpression $_1$ ?\nexpression $_2$ :\nfalse.\n-\n-\nexpression $_1$\n||\nexpression $_2$\nThis operation expresses\nlogical disjunction , meaning roughly\nthe same as the English word or.\n\nWe assume this syntactic form to be syntactic sugar for\nexpression $_1$ ?\ntrue :\nexpression $_2$.\n-\n-\n!\nexpression\nThis operation expresses\nlogical negation , meaning\nroughly the same as the English word not.\n\nThe value of the expression is true when\nexpression\nevaluates to false, and false when\nexpression\nevaluates to true.\n\nNotice that && and\n|| are syntactic forms,\nnot operators;\n! , on the other hand,\nfollows the evaluation rule of section.\n\nIt is a unary operator, which means that it takes only\none argument, whereas the arithmetic operators and primitive predicates\ndiscussed so far\nare binary , taking two arguments.\n\nThe operator\n! precedes its argument;\nwe call it a\nprefix operator.\n\nAnother prefix operator is\nthe numeric negation operator, an example of\nwhich is the expression - x\nin the abs functions above.\n\nAs an example of how these predicates are used, the condition that a number $x$ be in the range $5 < x < 10$ may\n\nbe expressed as\n\n```javascript\nx > 5 && x < 10\n```", + "token_count": 279, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Conditional Expressions and Predicates", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Functions_Conditional_Expressions_and_Predicates_3" + }, + { + "content": "be expressed as\n\nThe syntactic form && has lower precedence than the comparison operators > and < , and the conditional-expression syntactic form $\\cdots$ ? $\\cdots$ : $\\cdots$\n\nhas lower precedence than any other operator we have encountered so far, a property we used in the abs functions above.\n\nAs another example, we can declare a predicate to test whether one number is greater than or equal to another as\n\n```javascript\ngeq_example\n\nfunction greater_or_equal(x, y) {\n return x > y || x === y;\n}\n```\n\nor alternatively as\n\n```javascript\ngeq_example\n\nfunction greater_or_equal(x, y) {\n return ! (x < y);\n}\n```\n\n```javascript\ngeq_example\n\ngreater_or_equal(7, 4);\n```\n\n```javascript\nThe function greater_or_equal,\n\twhen applied to two numbers, behaves the same as the operator\n\t>=. Unary operators have\n```", + "token_count": 127, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Conditional Expressions and Predicates", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Functions_Conditional_Expressions_and_Predicates_4" + }, + { + "content": "One of our goals in this chapter is to isolate issues about thinking\nprocedurally.\n\nAs a case in point, let us consider that, in evaluating\noperator\ncombinations, the interpreter is itself following a procedure.\n-\n-\nTo evaluate\nan operator combination,\ndo the following:\n-\n- Evaluate the\noperand expressions\nof the combination.\n-\n-\n\n```javascript\nApply the function that is denoted by\n the operator to the arguments that are the values of\n the operands.\n```\n\nrecursive in nature; that is, it includes, as one of its steps, the need to invoke the rule itself.\n\nNotice how succinctly the idea of recursion can be used to express\n\n```javascript\n(2 + 4 * 6) * (3 + 12);\n```\n\nrequires that the evaluation rule be applied to four different\ncombinations.\n\nWe can obtain a picture of this process by\nrepresenting the combination in the form of a\nfigure.\n\nEach combination is represented by a\npercolate values\nupward form of the evaluation rule is an example of a general kind\nof process known as\ntree accumulation.\n\nTree representation, showing the value of each subexpression.\n\nNext, observe that the repeated application of the first step brings\nus to the point where we need to evaluate, not combinations, but\nprimitive expressions such as\nnumerals or names.\n\nWe take care of the primitive cases\n-\n-\nthe values of numerals are the numbers that they name,\nand\n-\n-\nthe values of\nnames are the objects associated\nwith those names in the environment.\n\nThe key point to\nnotice is the role of the\nnames\nin expressions.", + "token_count": 261, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Evaluating Operator Combinations", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Evaluating_Operator_Combinations_1" + }, + { + "content": "The key point to\nnotice is the role of the\nnames\nin expressions.\n\nIn an interactive language such as\nJavaScript,\nit is meaningless to speak of the value of an expression such as\nx + 1\nwithout specifying any information about the environment\nthat would provide a meaning for the\nname\nAs we shall see in chapter 3, the general notion of\nthe environment as providing a context in which evaluation takes place\nwill play an important role in our understanding of program execution.\n\nNotice that the evaluation rule given above does not handle For instance, evaluating\n\n```javascript\nconst x = 3;\n```\n\ndoes not apply\n\n```javascript\nan equality operator =\n```\n\nto two arguments, one of which is the value of the name declaration is precisely to associate\n\n```javascript\nconst x = 3;\n```\n\nis not a combination.)\n\nThe letters in const are\nrendered in bold to indicate that it\nkeyword in JavaScript.\n\nKeywords carry a\nparticular meaning, and thus cannot be used as names.\n\nA keyword or a\ncombination of keywords in a statement instructs the JavaScript\ninterpreter to treat the statement in a special way.\n\nEach such\nsyntactic form has its own evaluation rule.\n\nThe\nvarious kinds of statements and expressions (each with its associated\nevaluation rule) constitute the\n\nThe letters in const are\nrendered in bold to indicate that it\nkeyword in JavaScript.\n\nKeywords carry a\nparticular meaning, and thus cannot be used as names.\n\nA keyword or a\ncombination of keywords in a statement instructs the JavaScript\ninterpreter to treat the statement in a special way.\n\nEach such\nsyntactic form has its own evaluation rule.\n\nThe\nvarious kinds of statements and expressions (each with its associated\nevaluation rule) constitute the", + "token_count": 285, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Evaluating Operator Combinations", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Evaluating_Operator_Combinations_2" + }, + { + "content": "Functions,\nas introduced above, are much like ordinary mathematical functions.\n\nThey\nspecify a value that is determined by one or more parameters.\n\nBut there\nis an important difference between mathematical functions and computer\nfunctions.\n\nComputer functions\nmust be effective.\n\nAs a case in point, consider the problem of computing square\nroots.\n\nWe can define the square-root function as\n\\[\n\\sqrt{x}\\ =\\text{ the }y\\text{ such that }y \\geq 0\\text{ and }\ny^2\\ =\\ x\n\\]\nThis describes a perfectly legitimate mathematical function.\n\nWe could\nuse it to recognize whether one number is the square root of another, or\nto derive facts about square roots in general.\n\nOn the other hand, the\ndefinition does not describe a\ncomputer function.\n\nIndeed, it tells us almost nothing about how to actually find the square\nroot of a given number.\n\nIt will not help matters to rephrase this\ndefinition in\npseudo-JavaScript:\n\n```javascript\nfunction sqrt(x) {\n return the y $\\texttt{with}$ y >= 0 && square(y) === x;\n}\n```\n\nThis only begs the question.\n\nThe contrast between mathematical function and computer function is a reflection of the general distinction between describing properties of things and describing how to do\n\nthings, or, as it is sometimes referred to, the distinction between\n\nHow does one compute\ns method of successive approximations, which says that whenever\nwe have a guess $y$ for the value of the square\nroot of a number $x$ , we can perform a simple\nmanipulation to get a better guess (one closer to the actual square root)\nby averaging $y$ with\n$x/y$.", + "token_count": 257, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Example: Square Roots by Newton s Method", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Example_Square_Roots_by_Newton_s_Method_1" + }, + { + "content": "How does one compute\ns method of successive approximations, which says that whenever\nwe have a guess $y$ for the value of the square\nroot of a number $x$ , we can perform a simple\nmanipulation to get a better guess (one closer to the actual square root)\nby averaging $y$ with\n$x/y$.\n\n1:\n\\[\n\\begin{array}{lll}\n\\textrm{Guess} & \\textrm{Quotient} & \\textrm{Average}\\\\[1em]\n1 & {\\displaystyle \\frac{2}{1} = 2} & {\\displaystyle \\frac{(2+1)}{2} = 1.5} \\\\[1em]\n1.5 & {\\displaystyle \\frac{2}{1.5} = 1.3333} & {\\displaystyle \\frac{(1.3333+1.5)}{2} = 1.4167} \\\\[1em]\n1.4167 & {\\displaystyle \\frac{2}{1.4167} = 1.4118} & {\\displaystyle \\frac{(1.4167+1.4118)}{2} = 1.4142} \\\\[1em]\n1.4142 & \\ldots & \\ldots\n\\end{array}\n\\]\nContinuing this process, we obtain better and better approximations to the\nsquare root.\n\nNow let s formalize the process in terms of functions.\n\nWe start with\na value for the\nfunction:\n\n```javascript\nsqrt_iter\n is_good_enough\n improve\n sqrt_iter_example\n\nfunction sqrt_iter(guess, x) {\n return is_good_enough(guess, x)\n ? guess\n : sqrt_iter(improve(guess, x), x);\n}\n```\n\n```javascript\nsqrt_iter_example\n\nsqrt_iter(3, 25);\n```\n\nA guess is improved by averaging it with the quotient of the radicand and the old guess:\n\n```javascript\nimprove\n average_definition\n improve_example\n\nfunction improve(guess, x) {\n return average(guess, x / guess);\n}\n```\n\n```javascript\nimprove_example\n\nimprove(3, 25);\n```\n\nwhere\n\n```javascript\naverage_definition\n average_example\n\nfunction average(x, y) {\n return (x + y) / 2;\n}\n```\n\n```javascript\naverage_example\n\naverage(3, 6);\n```\n\nWe also have to say what we mean by good enough.\n\nThe\nfollowing will do for illustration, but it is not really a very good\ntest.\n\n(See exercise.)\nThe idea is to improve the answer until it is close enough so that its\nsquare differs from the radicand by less than a predetermined\ntolerance (here 0.001):\n\n```javascript\nis_good_enough\n abs_definition\n square_definition\n is_good_enough_example\n\nfunction is_good_enough(guess, x) {\n return abs(square(guess) - x) < 0.001;\n}\n```\n\n```javascript\nis_good_enough_example\n\nis_good_enough(1.41, 2);\n```\n\nFinally, we need a way to get started.", + "token_count": 304, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Example: Square Roots by Newton s Method", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Example_Square_Roots_by_Newton_s_Method_2" + }, + { + "content": "Finally, we need a way to get started.\n\nFor instance, we can always guess\nthat the square root of any number\nis 1:\n\n```javascript\nsqrt\n sqrt_iter\n sqrt_example_2\n 2.2360688956433634\n\nfunction sqrt(x) {\n return sqrt_iter(1, x);\n}\n```\n\nIf we type these declarations to the interpreter, we can use function:\n\n```javascript\nsqrt_example\n sqrt\n\nsqrt(9);\n```\n\n```javascript\nsqrt_example_2\n\nsqrt(5);\n```\n\n```javascript\nsqrt_example_3\n sqrt\n\nsqrt(100 + 37);\n```\n\n```javascript\nsqrt_example_4\n 1.7739279023207892\n sqrt\n\nsqrt(sqrt(2) + sqrt(3));\n```\n\n```javascript\nsqrt_example_5\n sqrt\n\nsquare(sqrt(1000));\n```\n\nThe\nlanguage we have introduced so far is sufficient for writing any purely\nnumerical program that one could write in, say, C or Pascal.\n\nThis might\nseem surprising, since we have not included in our language any iterative\nThe function sqrt_iter,\non the other hand, demonstrates how iteration can be accomplished using no\nspecial construct other than the ordinary ability to call a\nfunction.", + "token_count": 141, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Example: Square Roots by Newton s Method", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Functions_Example_Square_Roots_by_Newton_s_Method_3" + }, + { + "content": "One easy way to get started at programming is to examine some typical\ninteractions with an interpreter for the\nJavaScript language.\n\nYou type\na statement,\nand the interpreter responds by displaying the\nresult of its evaluating that\n\n```javascript\nOne kind of statement you might type is an\n expression statement, which consists of an\n expression followed by a semicolon.\n```\n\n(More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present JavaScript with the program\n\n```javascript\n486;\n```\n\nthe interpreter will respond by printing\n\nExpressions representing numbers may be combined with operators (such + * ) to form a\n\n```javascript\n137 + 349;\n```\n\n```javascript\n1000 - 334;\n```\n\n```javascript\n5 * 99;\n```\n\n```javascript\n10 / 4;\n```\n\n```javascript\n2.7 + 10;\n```\n\nExpressions such as these, which contain other expressions as components, are called combinations. operator symbol in the middle, and operand expressions to the left\n\nand right of it, are called operator combinations.\n\nThe convention of placing the operator between the operands is\nknown as\ninfix notation.\n\nIt follows the mathematical notation that\nyou are most likely familiar with from school and everyday life.\n\nAs in mathematics, operator combinations can be nested , that\nis, they can have operands that\n\n```javascript\n(3 * 5) + (10 - 6);\n```\n\nAs usual,\n\n```javascript\n3 * 5 + 10 / 2;\n```\n\nstands for\n\n```javascript\n(3 * 5) + (10 / 2);\n```\n\nWe say that * and\n/ have\nhigher precedence\nthan + and\n-.\n\nSequences of additions and\nsubtractions are read from left to right, as are sequences of\nmultiplications and divisions.\n\nThus,\n\n```javascript\n-6\n\n1 - 5 / 2 * 4 + 3;\n```\n\nstands for\n\n```javascript\n(1 - ((5 / 2) * 4)) + 3;\n```\n\nWe say that the operators + , - , * and / are left-associative.", + "token_count": 314, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Expressions", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Expressions_1" + }, + { + "content": "We say that the operators + , - , * and / are left-associative.\n\nThere is no limit (in principle) to the depth of such nesting and to the\noverall complexity of the expressions that the JavaScript interpreter\ncan evaluate.\n\nIt is we humans who might get confused by still relatively\nsimple expressions such as\n\n```javascript\n57\n\n3 * 2 * (3 - 5 + 4) + 27 / 6 * 10;\n```\n\nwhich the interpreter would readily evaluate to be 57.\n\nWe can help\nourselves by writing such an expression in the form\n\n```javascript\n3 * 2 * (3 - 5 + 4)\n+\n27 / 6 * 10;\n```\n\nto visually separate the major components of the expression.\n\nEven with complex expressions, the interpreter always operates in the\nsame basic cycle: It reads\na statement typed by the user,\nevaluates the\nstatement,\nand prints the result.\n\nThis mode of operation is often expressed by saying\nthat the interpreter runs in a\nread-evaluate-print loop.\n\nObserve in particular that it is not necessary to explicitly instruct the\ninterpreter to print the value of the\nstatement.", + "token_count": 185, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Expressions", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Expressions_2" + }, + { + "content": "The function sqrt\nis our first example of a process defined by a set of mutually\ndefined functions.\n\nNotice that the\n\n```javascript\ndeclaration of\n sqrt_iter\n```\n\nis\nrecursive ; that is, the\nfunction\nis defined in terms of itself.\n\nThe idea of being able to define a\nfunction\nin terms of itself may be disturbing; it may seem unclear how such a\ncircular definition could make sense at all, much less\nspecify a well-defined process to be carried out by a computer.\n\nThis will\nbe addressed more carefully in\nsection.\n\nBut first\nlet s consider some other important points illustrated by the\n\nObserve that the problem of computing square roots breaks up naturally\ninto a number of subproblems:\nfunction.\n\nThe entire\n\n```javascript\nfunctions\n\t(shown in figure)\n```\n\nthat mirrors the decomposition of the problem into subproblems.\n\nThe importance of this\nthe first ten lines, the next\nten lines, the next ten lines, and so on.\n\nRather, it is crucial that\neach\nfunction\naccomplishes an identifiable task that can be used as a module in defining\nother\nfunctions.\n\nFor example, when we define the\nis_good_enough function\nin terms of\nfunction\nas a\nblack box.\n\nWe are not at that moment concerned with\nhow the\nfunction\ncomputes its result, only with the fact that it computes the\nsquare.\n\nThe details of how the square is computed can be suppressed,\nto be considered at a later time.\n\nIndeed, as far as the\nis_good_enough function\nis concerned,\nfunction\nbut rather an abstraction of a\nfunction,\na so-called\nfunctional abstraction.\n\nAt this level of abstraction, any\nfunction\nthat computes the square is equally good.\n\nThus, considering only the values they return, the following two\nfunctions\nsquaring a number should be indistinguishable.\n\nEach takes a numerical\nargument and produces the square of that number as the value.", + "token_count": 301, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Functions as Black-Box Abstractions", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Black-Box_Abstractions_1" + }, + { + "content": "Each takes a numerical\nargument and produces the square of that number as the value.\n\n```javascript\nsquare_example\n\nfunction square(x) {\n return x * x;\n}\n```\n\n```javascript\nsquare_example\n\nfunction square(x) {\n return math_exp(double(math_log(x)));\n}\nfunction double(x) {\n return x + x;\n}\n```\n\nSo a\nfunction\nshould be able to suppress detail.\n\nThe users of the\nfunction\nmay not have written the\nfunction\nthemselves, but may have obtained it from another programmer as a\nblack box.\n\nA user should not need to know how the\nfunction\nis implemented in order to use it.\n\nOne detail of a\nfunctions\nimplementation that should not matter to the user of the\nfunction\nis the implementer s choice of names for the\nfunctions parameters.\n\nThus, the following\nfunctions\nshould not be distinguishable:\n\n```javascript\nsquare_example\n\nfunction square(x) {\n return x * x;\n}\n```\n\n```javascript\nsquare_example\n\nfunction square(y) {\n return y * y;\n}\n```\n\nThis principle that the meaning of a\nfunction\nshould be independent of the parameter names used by its\nauthor seems on the surface to be self-evident, but its\nconsequences are profound.\n\nThe simplest consequence is that the\nparameter names of a\nfunction\nmust be local to the body of the\nfunction.\n\nFor example, we used\n\n```javascript\nin the declaration of\n is_good_enough\n```\n\nin our square-root function:\n\n```javascript\nis_good_enough_example\n abs_definition\n square_definition\n\nfunction is_good_enough(guess, x) {\n return abs(square(guess) - x) < 0.001;\n}\n```\n\nThe intention of the author of\nis_good_enough\nis to determine if the square of the first argument is within a given\ntolerance of the second argument.\n\nWe see that the author of\nis_good_enough\nused the name\nis_@good_@enough\nmust be a different\nfunction\nis_good_enough,\nbecause that value of\nis_good_enough\nafter\n\nIf the parameters were not local to the bodies of their respective functions, then the parameter is_@good_@enough, and the behavior of is_good_enough would depend upon\n\nwhich version of", + "token_count": 309, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Functions as Black-Box Abstractions", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Black-Box_Abstractions_2" + }, + { + "content": "which version of\n\nA\nparameter of a function\nhas a very special role in the\nfunction declaration,\nin that it doesn t matter what name the\nparameter has.\n\nSuch a name is called\nbound, and we say that the function declaration\nbinds its\nparameters.\n\nThe meaning of a\nfunction declaration is unchanged if a bound name\nis consistently renamed throughout the\ndeclaration.\nname\nis not bound, we say that it is\nfree.\n\nThe set of\nstatements\nfor which a binding\ndeclares\na name is called the\nscope of that name.\n\nIn a\nfunction declaration, the bound names\ndeclared as the\nparameters of the function\nhave the body of the\nfunction\nas their scope.\n\nIn the\ndeclaration of is_good_enough\nabove,\nnames\nbut\nis_good_enough\nshould be independent of the names we choose for\ncapturing the\nname\nis_good_enough\nis not independent of the\nchoice of its free names,\nhowever.\n\nIt surely depends upon the fact\n(external to this declaration)\nthat the name\nfor computing the absolute value of a number.\n\nThe function is_good_enough\nwill compute a different function if we substitute\n\n```javascript\nmath_cos\n\t(the primitive cosine function)\n```\n\nfor declaration.\n\nWe have one kind of name isolation available to us so far:\nThe parameters of a function\nare local to the body of the\nfunction.\n\nThe square-root program illustrates another way in which we would like to\ncontrol the use of names.\nfunctions:\n\n```javascript\nsqrt_example_2\n abs_definition\n square_definition\n average_definition\n\nfunction sqrt(x) {\n return sqrt_iter(1, x);\n}\nfunction sqrt_iter(guess, x) {\n return is_good_enough(guess, x)\n ? guess\n : sqrt_iter(improve(guess, x), x);\n}\nfunction is_good_enough(guess, x) {\n return abs(square(guess) - x) < 0.001;\n}\nfunction improve(guess, x) {\n return average(guess, x / guess);\n}\n```\n\nThe problem with this program is that the only function that is important to users of functions\n\n```javascript\n(sqrt_iter,\n\tis_good_enough,\n```", + "token_count": 299, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Functions as Black-Box Abstractions", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Black-Box_Abstractions_3" + }, + { + "content": "The problem with this program is that the only function that is important to users of functions\n\nand\ndeclare any other function\ncalled\nis_good_enough\nas part of another program to work together\nwith the square-root program, because\nfunctions,\nmany numerical functions are computed as successive approximations and\nthus might have\nfunctions\nnamed\nis_good_enough\nand\nfunctions.\n\nWe would like to localize the\nsubfunctions,\nhiding them inside\nis_good_enough function.\n\nTo make this possible, we allow a\nfunction\nto have\ninternal declarations that are local to that\nfunction.\n\nFor example, in the square-root problem we can write\n\n```javascript\nsqrt_example_2\n 2.2360688956433634\n abs_definition\n square_definition\n average_definition\n\nfunction sqrt(x) {\n function is_good_enough(guess, x) {\n return abs(square(guess) - x) < 0.001;\n }\n function improve(guess, x) {\n return average(guess, x / guess);\n }\n function sqrt_iter(guess, x) {\n return is_good_enough(guess, x)\n ? guess\n : sqrt_iter(improve(guess, x), x);\n }\n return sqrt_iter(1, x);\n}\n```\n\n```javascript\nAny matching pair of braces designates a block, and\n\tdeclarations inside the block are local to the block.\n```\n\nSuch nesting of\ndeclarations,\ncalled block structure , is basically the right solution to the\nsimplest name-packaging problem.\n\nBut there is a better idea lurking here.\n\nIn addition to internalizing the\ndeclarations of the auxiliary functions,\nwe can simplify them.\n\nSince\ndeclaration\nof\nfunctions\nis_good_enough,\n\n```javascript\nsqrt_iter,\n which are declared internally to\n```\n\nfunctions.\n\nInstead, we allow\nname\nin the internal\ndeclarations,\nas shown below.\n\nThen\nfunction\nlexical scoping.\n\n```javascript\nsqrt_example_2\n abs_definition\n square_definition\n average_definition\n\nfunction sqrt(x) {\n function is_good_enough(guess) {\n return abs(square(guess) - x) < 0.001;\n }\n function improve(guess) {\n return average(guess, x / guess);\n }\n function sqrt_iter(guess) {\n return is_good_enough(guess)\n ? guess\n : sqrt_iter(improve(guess));\n }\n return sqrt_iter(1);\n}\n```\n\nWe will use block structure extensively to help us break up large programs\ninto tractable pieces.\n\n60.", + "token_count": 290, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Functions as Black-Box Abstractions", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Black-Box_Abstractions_4" + }, + { + "content": "60.\n\nIt appears in most advanced programming languages and is an\nimportant tool for helping to organize the construction of large programs.", + "token_count": 22, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Functions as Black-Box Abstractions", + "chunk_index": 5, + "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Black-Box_Abstractions_5" + }, + { + "content": "A critical aspect of a programming language is the means it provides for using objects, and our first such means are constants. constant whose value\n\nis the object.\n\n```javascript\nIn JavaScript, we name constants with\n\tconstant declarations.\n```\n\n```javascript\nvar_size\n\nconst size = 2;\n```\n\ncauses the interpreter to associate the value 2 with the name\n\n```javascript\nsize_use_1\n var_size\n 2\n\nsize;\n```\n\n```javascript\nsize_use_2\n var_size\n 10\n\n5 * size;\n```\n\nHere are further examples of the use of const:\n\n```javascript\npi\n\nconst pi = 3.14159;\n```\n\n```javascript\nradius\n\nconst radius = 10;\n```\n\n```javascript\npi_radius_radius\n 314.159\n pi\n radius\n\npi * radius * radius;\n```\n\n```javascript\ncircumference_definition\n pi\n radius\n\nconst circumference = 2 * pi * radius;\n```\n\n```javascript\n62.8318\n circumference_use\n circumference_definition\n\ncircumference;\n```\n\nConstant is our language s simplest means of abstraction, for it allows us to use simple names to refer to the results of compound operations,\n\nsuch as the JavaScript program usually consists of a large number of relatively simple functions.\n\nIt should be clear that the possibility of associating values with\nnames and later retrieving them means that the interpreter must\nmaintain some sort of memory that keeps track of the name-object\npairs.\n\nThis memory is called the\nenvironment\n(more precisely the\nprogram environment,\nsince we will see later that a\ncomputation may involve a number of different\nenvironments).", + "token_count": 222, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Naming and the Environment", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_Naming_and_the_Environment_1" + }, + { + "content": "```javascript\nTo evaluate a function application, the interpreter follows the process\n\tdescribed in section.\n```\n\nThat is, the interpreter evaluates the elements of the application and applies the function (which is the value of the function expression of the application)\n\nto the arguments (which are the values of the argument expressions of the application).\n\n```javascript\nWe can assume that the application of primitive\n\tfunctions is handled by the interpreter or libraries.\n```\n\nFor compound\nfunctions,\nthe application process is as follows:\n-\n-\nTo apply a compound\nfunction\nto arguments,\nevaluate the return expression of the function\nwith each\nparameter replaced by the corresponding argument.\n\nTo illustrate this process, let s evaluate the\napplication\n\n```javascript\nf_of_five\n f\n 136\n\nf(5)\n\nf(5);\n```\n\nwhere\nfunction declared\nin section.\n\nWe begin by retrieving the\nreturn expression\nof\n\n```javascript\nf\n\nsum_of_squares(a + 1, a * 2)\n```\n\nThen we replace the parameter\n\n```javascript\nsum_of_squares(5 + 1, 5 * 2)\n```\n\nThus the problem reduces to the evaluation of an application with two arguments and\n\n```javascript\na function expression\n\tsum_of_squares.\n```\n\nEvaluating this\napplication\ninvolves three subproblems.\n\nWe must evaluate the\nfunction expression\nto get the\nfunction\nto be applied, and we must evaluate the\nargument expressions\nto get the arguments.\n\nNow\n5 + 1\nproduces 6 and\n5 * 2\nproduces 10, so we must apply the\nsum_of_squares function\nto 6 and 10.\n\nThese values are substituted for the\nparameters\nsum_of_squares,\nreducing the expression to\n\n```javascript\nsquare(6) + square(10)\n```\n\nIf we use the declaration of\n\n```javascript\n(6 * 6) + (10 * 10)\n```\n\nwhich reduces by multiplication to\n\n```javascript\n36 + 100\n```\n\nand finally to\n\n```javascript\n136\n```\n\nThe process we have just described is called the substitution\nmodel for\nfunction\napplication.\n\nIt can be taken as a model that\ndetermines the meaning of\nfunction\napplication, insofar as the\nfunctions\nin this chapter are concerned.", + "token_count": 314, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "The Substitution Model for Function Application", + "chunk_index": 1, + "chunk_id": "Building_Abstractions_with_Functions_The_Substitution_Model_for_Function_Application_1" + }, + { + "content": "It can be taken as a model that\ndetermines the meaning of\nfunction\napplication, insofar as the\nfunctions\nin this chapter are concerned.\n\nHowever, there are two\npoints that should be stressed:\n-\n-\nThe purpose of the substitution is to help us think about\nfunction\napplication, not to provide a description of how the interpreter\nreally works.\n\nTypical interpreters do not evaluate\nfunction\napplications by manipulating the text of a\nfunction to substitute values for the\nparameters.\n\nIn practice, the substitution is\naccomplished by using a local environment for the\nparameters.\n\nWe will discuss this more fully in chapters 3 and\n4 when we examine the implementation of an interpreter in detail.\n-\n-\nOver the course of this book, we will present a sequence of\nincreasingly elaborate models of how interpreters work, culminating\nwith a complete implementation of an interpreter and compiler in\nchapter.\n\nThe substitution model is only the first of\nthese models a way to get started thinking formally\nabout the evaluation process.\n\nIn general, when\nthe use of\nfunctions\nwith mutable data, we will see that the substitution\nmodel breaks down and must be replaced by a more complicated model of\nfunction\napplication.\n\nAccording to the description of evaluation given in\nsection,\nthe interpreter first evaluates the\nfunction\nand\nargument expressions\nand then applies the resulting\nfunction\nto the resulting arguments.\n\nThis is not the only way to perform evaluation.\n\nAn alternative evaluation model would not evaluate the\narguments\nuntil their values were needed.\n\nInstead it would first substitute\nargument\nexpressions for parameters until it obtained an expression involving\nonly\noperators and primitive functions,\nand would then perform the evaluation.\n\nIf we\nused this method, the evaluation of\n\n```javascript\nf(5)\n```\n\nwould proceed according to the sequence of expansions", + "token_count": 294, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "The Substitution Model for Function Application", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_The_Substitution_Model_for_Function_Application_2" + }, + { + "content": "would proceed according to the sequence of expansions\n\n```javascript\nsum_of_squares(5 + 1, 5 * 2)\n\nsquare(5 + 1) + square(5 * 2)\n\n(5 + 1) * (5 + 1) + (5 * 2) * (5 * 2)\n```\n\nfollowed by the reductions\n\n```javascript\n6 * 6 + 10 * 10\n\n 36 + 100\n\n 136\n```\n\nThis gives the same answer as our previous evaluation model, but the\nprocess is different.\n\nIn particular, the evaluations of\n5 + 1\nand\n5 * 2\nare each performed twice here, corresponding to the reduction of the\nexpression\n\n```javascript\nx * x\n```\n\nwith 5 + 1 and 5 * 2.\n\nThis alternative fully expand and then reduce\nevaluation method is known as\nnormal-order evaluation , in contrast to the evaluate\nthe arguments and then apply method that the interpreter actually\nuses, which is called\napplicative-order evaluation.\n\nIt can be shown that, for\nfunction\napplications that can be modeled using substitution (including all the\nfunctions\nin the first two chapters of this book) and that yield legitimate values,\nnormal-order and applicative-order evaluation produce the same value.\n\n(See exercise\nfor an instance of an illegitimate value where normal-order\nand applicative-order evaluation do not give the same result.)\n\nJavaScript uses applicative-order evaluation, partly because of the additional efficiency obtained from avoiding multiple evaluations of expressions such as those illustrated with\n\n```javascript\n5 + 1\n\tand 5 * 2\n```\n\nabove and, more significantly, because normal-order evaluation\nbecomes much more complicated to deal with when we leave the realm of\nfunctions\nthat can be modeled by substitution.\n\nOn the other hand,\nnormal-order evaluation can be an extremely valuable tool, and we will\ninvestigate some of its implications in chapters 3 and 4.", + "token_count": 285, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "The Substitution Model for Function Application", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Functions_The_Substitution_Model_for_Function_Application_3" + }, + { + "content": "We began this book by studying processes and by describing processes\nin terms of\nfunctions\nwritten in\nJavaScript.\n\nTo explain the meanings of these\nfunctions,\nwe used a succession of models of evaluation: the\nsubstitution model of chapter , the environment model of\nchapter , and the metacircular evaluator of chapter.\n\nOur\nexamination of the metacircular evaluator, in particular, dispelled much of\nthe mystery of how\nJavaScript-like languages are interpreted.\n\nBut even the metacircular evaluator leaves important questions\nunanswered, because it fails to elucidate the mechanisms of control in a\nJavaScript\nsystem.\n\nFor instance, the evaluator does not explain how the\nevaluation of a subexpression manages to return a value to the\nexpression that uses this value.\n\n```javascript\nAlso, the evaluator does not explain how some recursive\n functions can generate iterative processes (that is, be\n evaluated using constant space) whereas other recursive\n functions will generate recursive processes.\n```\n\nWe\nwill describe processes in terms of the step-by-step\noperation of a traditional computer.\n\nSuch a computer, or\nregister machine , sequentially executes\ninstructions that\nmanipulate the contents of a fixed set of storage elements called\nregisters.\n\nA typical register-machine instruction applies a\nprimitive operation to the contents of some registers and assigns the\nresult to another register.\n\nOur descriptions of processes executed by\nregister machines will look very much like machine-language\nprograms for traditional computers.\n\nHowever, instead of focusing on\nthe machine language of any particular computer, we will examine\nseveral\nJavaScript\nfunctions\nand design a specific register machine to\nexecute each\nfunction.\n\nThus, we will approach our task from the\nperspective of a hardware architect rather than that of a\nmachine-language computer programmer.\n\nIn designing register machines,\nwe will develop mechanisms for implementing important programming\nconstructs such as recursion.\n\nWe will also present a language for\ndescribing designs for register machines.", + "token_count": 301, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": null, + "subsection": null, + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Computing_with_Register_Machines_1" + }, + { + "content": "We will also present a language for\ndescribing designs for register machines.\n\nIn\nsection we will\nimplement a\nJavaScript\nprogram that uses these descriptions to simulate the machines we design.\n\nMost of the primitive operations of our register machines are very\nsimple.\n\nFor example, an operation might add the numbers fetched from\ntwo registers, producing a result to be stored into a third register.\n\nSuch an operation can be performed by easily described hardware.\n\nIn\norder to deal with list structure, however, we will also use the\nmemory operations\nhead,\ntail,\nand\npair,\nwhich require an elaborate storage-allocation mechanism.\n\nIn\nsection we study their\nimplementation in terms of more elementary operations.\n\nIn section , after we have accumulated\nexperience formulating simple\nfunctions\nas register machines, we will design a\nmachine that carries out the algorithm described by the metacircular\nevaluator of section.\n\nThis will fill in\nthe gap in our understanding of how\nJavaScript programs\nare interpreted, by providing an explicit model for the mechanisms of\ncontrol in the evaluator.\n\nIn section we will study a simple\ncompiler that translates\nJavaScript\nprograms into sequences of instructions that can be executed directly with\nthe registers and operations of the evaluator register machine.", + "token_count": 201, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": null, + "subsection": null, + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Computing_with_Register_Machines_2" + }, + { + "content": "In order to gain a good understanding of the design of register machines,\nwe must test the machines we design to see if they perform as expected.\n\nOne way to test a design is to hand-simulate the operation of the\ncontroller, as in exercise.\n\nBut this is\nextremely tedious for all but the simplest machines.\n\nIn this section we\nconstruct a simulator for machines described in the register-machine\nlanguage.\n\nThe simulator is a\nJavaScript\nprogram with\nfour interface\nfunctions.\n\nThe first uses a description of a register\nmachine to construct a model of the machine (a data structure whose\nparts correspond to the parts of the machine to be simulated), and the\nother three allow us to simulate the machine by manipulating the\nmodel:\n-\n-\nmake_machine(register-names, operations, controller)\nconstructs and returns a model of the machine with the given\nregisters, operations, and controller.\n-\n-\nset_register_contents(machine-model, register-name, value)\nstores a value in a simulated register in the given machine.\n-\n-\nget_register_contents(machine-model, register-name)\nreturns the contents of a simulated register in the given machine.\n-\n-\nstart(machine-model)\nsimulates the execution of the given machine, starting from the\nbeginning of the controller sequence and stopping when it reaches the\nend of the sequence.\n\nAs an example of how these functions are used, we can define gcd_machine to be a model of the GCD machine of section as follows:\n\n```javascript\ngcd_machine_example\n gcd_machine\n start\n\nset_register_contents(gcd_machine, \"a\", 206);\nset_register_contents(gcd_machine, \"b\", 40);\nstart(gcd_machine);\nget_register_contents(gcd_machine, \"a\");\n```\n\n```javascript\ngcd_machine\n make_machine\n gcd_machine_example\n 2\n\nconst gcd_machine =\n make_machine(\n list(\"a\", \"b\", \"t\"),\n list(list(\"rem\", (a, b) => a % b),\n list(\"=\", (a, b) => a === b)),\n list(\n \"test_b\",\n test(list(op(\"=\"), reg(\"b\"), constant(0))),\n branch(label(\"gcd_done\")),\n assign(\"t\", list(op(\"rem\"), reg(\"a\"), reg(\"b\"))),\n assign(\"a\", reg(\"b\")),\n assign(\"b\", reg(\"t\")),\n go_to(label(\"test_b\")),\n \"gcd_done\"));\n```\n\nThe first argument to\nmake_machine\nis a list of register names.", + "token_count": 296, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_A_Register-Machine_Simulator_1" + }, + { + "content": "The first argument to\nmake_machine\nis a list of register names.\n\nThe next argument is a table (a list of\ntwo-element lists) that pairs each operation name with a\nJavaScript function\nthat implements the operation (that is, produces the same output value\ngiven the same input values).\n\nThe last argument specifies the controller\nas a list of labels and machine instructions, as in\nsection.\n\nTo compute GCDs with this machine, we set the input registers, start the machine, and examine the result when the simulation terminates:\n\n```javascript\nset_register_contents_a\n gcd_machine\n start\n 'done'\n\nset_register_contents(gcd_machine, \"a\", 206);\n```\n\n```javascript\nset_register_contents_b\n set_register_contents_a\n 'done'\n\nset_register_contents(gcd_machine, \"b\", 40);\n```\n\n```javascript\nstart_gcd_machine\n set_register_contents_b\n 'done'\n\nstart(gcd_machine);\n```\n\n```javascript\nget_register_contents_a\n start_gcd_machine\n 2\n\nget_register_contents(gcd_machine, \"a\");\n```\n\nThis computation will run much more slowly than a function written in JavaScript, because we will simulate low-level machine instructions, such as", + "token_count": 138, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_A_Register-Machine_Simulator_2" + }, + { + "content": "The assembler calls\nmake_execution_function\nto generate the execution\nfunction\nfor a controller instruction.\n\nLike the\nfunction\nin the evaluator of section ,\nthis dispatches on the type of instruction to generate the appropriate\nexecution\nfunction.\n\n```javascript\nThe details of these execution functions determine the\n meaning of the individual instructions in the register-machine language.\n```\n\n```javascript\ngcd_machine_complete_example\n\nfunction make_execution_function(inst, labels, machine,\n pc, flag, stack, ops) {\n const inst_type = type(inst);\n return inst_type === \"assign\"\n ? make_assign_ef(inst, machine, labels, ops, pc)\n : inst_type === \"test\"\n ? make_test_ef(inst, machine, labels, ops, flag, pc)\n : inst_type === \"branch\"\n ? make_branch_ef(inst, machine, labels, flag, pc)\n : inst_type === \"go_to\"\n ? make_go_to_ef(inst, machine, labels, pc)\n : inst_type === \"save\"\n ? make_save_ef(inst, machine, stack, pc)\n : inst_type === \"restore\"\n ? make_restore_ef(inst, machine, stack, pc)\n : inst_type === \"perform\"\n ? make_perform_ef(inst, machine, labels, ops, pc)\n : error(inst, \"unknown instruction type -- assemble\");\n}\n```\n\n```javascript\nmake_execution_function\n make_assign\n make_test\n test_instruction_syntax\n make_branch_5\n branch_branch_dest\n make_go_to\n go_to_go_to_dest\n make_save\n save_restore\n make_perform\n perform_perform_action\n gcd_machine_complete_example\n\nfunction make_execution_function(inst, labels, machine,\n pc, flag, stack, ops) {\n return type(inst) === \"assign\"\n ? make_assign_ef(inst, machine, labels, ops, pc)\n : type(inst) === \"test\"\n ? make_test_ef(inst, machine, labels, ops, flag, pc)\n : type(inst) === \"branch\"\n ? make_branch_ef(inst, machine, labels, flag, pc)\n : type(inst) === \"go_to\"\n ? make_go_to_ef(inst, machine, labels, pc)\n : type(inst) === \"save\"\n ? make_save_ef(inst, machine, stack, pc)\n : type(inst) === \"restore\"\n ? make_restore_ef(inst, machine, stack, pc)\n : type(inst) === \"push_marker_to_stack\"\n ? make_push_marker_to_stack_ef(machine, stack, pc)\n : type(inst) === \"revert_stack_to_marker\"\n ? make_revert_stack_to_marker_ef(machine, stack, pc)\n : type(inst) === \"perform\"\n ? make_perform_ef(inst, machine, labels, ops, pc)\n : error(inst, \"unknown instruction type -- assemble\");\n}\n```", + "token_count": 262, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": "Instructions and Their Execution Functions", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Instructions_and_Their_Execution_Functions_1" + }, + { + "content": "Like the\nfunction\nin the evaluator of section ,\nthis dispatches on the type of instruction to generate the appropriate\nexecution\nfunction.\n\n```javascript\nThe elements of the controller sequence\n received by make_machine and passed\n to assemble are strings (for\n labels) and tagged lists (for instructions). The tag in an instruction\n is a string that identifies the instruction type, such as\n \"go_to\", and the remaining elements\n of the list contains the arguments, such as the destination of the\n go_to.\n The dispatch in make_execution_function uses\n\n\t type_function\n gcd_machine_complete_example\n\nfunction type(instruction) { return head(instruction); }\n```\n\nThe tagged lists are constructed when the\nlist expression that is the third\nargument to make_machine is\nevaluated.\n\nEach argument to that\nlist is either a string (which\nevaluates to itself) or a call to a constructor for an instruction\ntagged list.\n\nFor example, assign(\"b\", reg(\"t\")) calls the constructor\nassign with arguments\n\"b\" and the result of calling the\nconstructor reg with the argument\n\"t\".\n\nThe constructors and their\narguments determine the syntax of the individual instructions in the\nregister-machine language.\n\nThe instruction constructors and selectors\nare shown below, along with the execution-function generators that use\nthe selectors.\n\nThe make_assign_ef function makes execution functions for assign instructions:\n\n```javascript\nmake_assign\n type_function\n make_operation_exp\n assign_reg_name\n gcd_machine_complete_example\n\nfunction make_assign_ef(inst, machine, labels, operations, pc) {\n const target = get_register(machine, assign_reg_name(inst));\n const value_exp = assign_value_exp(inst);\n const value_fun =\n is_operation_exp(value_exp)\n ? make_operation_exp_ef(value_exp, machine, labels, operations)\n : make_primitive_exp_ef(value_exp, machine, labels);\n return () => {\n set_contents(target, value_fun());\n advance_pc(pc);\n };\n}\n```\n\n```javascript\nThe function assign constructs\n\tassign instructions.\n\tThe selectors assign_@reg_@name and\n\tassign_value_exp extract the register name\n\tand value expression from an assign instruction.\n```\n\n```javascript\nassign_reg_name\n\n gcd_machine_complete_example\n\nfunction assign(register_name, source) {\n return list(\"assign\", register_name, source);\n}\nfunction assign_reg_name(assign_instruction) {\n return head(tail(assign_instruction));\n}\nfunction assign_value_exp(assign_instruction) {\n return head(tail(tail(assign_instruction)));\n}\n```", + "token_count": 293, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": "Instructions and Their Execution Functions", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Instructions_and_Their_Execution_Functions_2" + }, + { + "content": "The make_assign_ef function makes execution functions for assign instructions:\n\nThe function make_assign_ef looks up the register name\nwith\nget_register\nto produce the target register object.\n\nThe value expression is passed to\nmake_@operation_@exp_@ef\nif the value is the result of an operation, and\nit is passed\nto\nmake_@primitive_@exp_@ef\notherwise.\n\nThese\nfunctions\n(shown below)\nanalyze\nthe value expression and produce an execution\nfunction\nfor the value.\n\nThis is a\nfunction\nof no arguments, called\nvalue_fun,\nwhich will be evaluated during the simulation to produce the actual\nvalue to be assigned to the register.\n\nNotice that the work of looking\nup the register name and\nanalyzing\nthe value expression is performed\njust once, at assembly time, not every time the instruction is\nsimulated.\n\nThis saving of work is the reason we use execution\nfunctions,\nand corresponds directly to the saving in work we obtained by separating\nprogram analysis from execution in the evaluator of\nsection.\n\nThe result returned by\nmake_assign_ef\nis the execution\nfunction\nfor the\nfunction\nis called (by the machine model s\nfunction),\nit sets the contents of the target register to the result obtained by\nexecuting\nvalue_fun.\n\nThen it advances the\nfunction\n\n```javascript\nadvance_pc\n gcd_machine_complete_example\n\nfunction advance_pc(pc) {\n set_contents(pc, tail(get_contents(pc)));\n}\n```\n\n```javascript\nThe function\n\tadvance_pc\n```\n\nis the normal termination for all instructions except go_to.\n\n```javascript\nThe function\n\tmake_test_ef\n```\n\nhandles\nfunction\nfor it.\n\nAt simulation time, the\nfunction\nfor the condition is called, the result is assigned to the\n\n```javascript\nmake_test\n advance_pc\n gcd_machine_complete_example\n\nfunction make_test_ef(inst, machine, labels, operations, flag, pc) {\n const condition = test_condition(inst);\n if (is_operation_exp(condition)) {\n const condition_fun = make_operation_exp_ef(\n condition, machine,\n labels, operations);\n return () => {\n set_contents(flag, condition_fun());\n advance_pc(pc);\n };\n } else {\n error(inst, \"bad test instruction -- assemble\");\n }\n}\n```", + "token_count": 288, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": "Instructions and Their Execution Functions", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_Instructions_and_Their_Execution_Functions_3" + }, + { + "content": "At simulation time, the\nfunction\nfor the condition is called, the result is assigned to the\n\n```javascript\nThe function\n\ttest constructs\n\ttest instructions. The selector\n\ttest_condition extracts the condition\n\tfrom a test.\n\n\t test_instruction_syntax\n gcd_machine_complete_example\n\nfunction test(condition) { return list(\"test\", condition); }\n\nfunction test_condition(test_instruction) {\n return head(tail(test_instruction));\n}\n```\n\nThe execution\nfunction\nfor a\nmake_branch_ef\nfunction\nenforces this.\n\nNotice also that the label is looked up at assembly time,\nnot each time the\n\n```javascript\nmake_branch_5\n gcd_machine_complete_example\n\nfunction make_branch_ef(inst, machine, labels, flag, pc) {\n const dest = branch_dest(inst);\n if (is_label_exp(dest)) {\n const insts = lookup_label(labels, label_exp_label(dest));\n return () => {\n if (get_contents(flag)) {\n set_contents(pc, insts);\n } else {\n advance_pc(pc);\n }\n };\n } else {\n error(inst, \"bad branch instruction -- assemble\");\n }\n}\n```\n\n```javascript\nThe function branch\n\tconstructs branch instructions. The\n\tselector\n\tbranch_dest extracts\n\tthe destination from a branch.\n\n\t branch_branch_dest\n gcd_machine_complete_example\n\nfunction branch(label) { return list(\"branch\", label); }\n\nfunction branch_dest(branch_instruction) {\n return head(tail(branch_instruction));\n}\n```\n\nA go_to instruction is similar to a branch, except that the destination may be specified either as a label or as a register, and there\n\nis no condition to check the\n\n```javascript\nmake_go_to\n is_register_exp\n gcd_machine_complete_example\n\nfunction make_go_to_ef(inst, machine, labels, pc) {\n const dest = go_to_dest(inst);\n if (is_label_exp(dest)) {\n const insts = lookup_label(labels, label_exp_label(dest));\n return () => set_contents(pc, insts);\n } else if (is_register_exp(dest)) {\n const reg = get_register(machine, register_exp_reg(dest));\n return () => set_contents(pc, get_contents(reg));\n } else {\n error(inst, \"bad go_to instruction -- assemble\");\n }\n}\n```\n\n```javascript\nThe function go_to constructs\n\tgo_to instructions. The selector\n\tgo_to_dest extracts the destination from a\n\tgo_to instruction.\n\n\t go_to_go_to_dest\n make_go_to\n gcd_machine_complete_example\n\nfunction go_to(label) { return list(\"go_to\", label); }\n\nfunction go_to_dest(go_to_instruction) {\n return head(tail(go_to_instruction));\n}\n```\n\nThe stack instructions", + "token_count": 275, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": "Instructions and Their Execution Functions", + "chunk_index": 4, + "chunk_id": "Computing_with_Register_Machines_Instructions_and_Their_Execution_Functions_4" + }, + { + "content": "The stack instructions\n\n```javascript\nmake_save\n pop\n gcd_machine_complete_example\n\nfunction make_save_ef(inst, machine, stack, pc) {\n const reg = get_register(machine, stack_inst_reg_name(inst));\n return () => {\n push(stack, get_contents(reg));\n advance_pc(pc);\n };\n}\nfunction make_restore_ef(inst, machine, stack, pc) {\n const reg = get_register(machine, stack_inst_reg_name(inst));\n return () => {\n set_contents(reg, pop(stack));\n advance_pc(pc);\n };\n}\n```\n\n```javascript\nThe functions save and\n\trestore construct\n\tsave and restore instructions. The\n\tselector\n\tstack_inst_reg_name\n\textracts the register name from such instructions.\n\n\t save_restore\n\t make_save\n\t gcd_machine_complete_example\n\nfunction save(reg) { return list(\"save\", reg); }\n\nfunction restore(reg) { return list(\"restore\", reg); }\n\nfunction stack_inst_reg_name(stack_instruction) {\n return head(tail(stack_instruction));\n}\n```\n\nThe final instruction type, handled by\nmake_perform_ef,\ngenerates an execution\nfunction\nfor the action to be performed.\n\nAt simulation time, the action\nfunction\nis executed and the\n\n```javascript\nmake_perform\n is_register_exp\n gcd_machine_complete_example\n\nfunction make_perform_ef(inst, machine, labels, operations, pc) {\n const action = perform_action(inst);\n if (is_operation_exp(action)) {\n const action_fun = make_operation_exp_ef(action, machine,\n labels, operations);\n return () => {\n action_fun();\n advance_pc(pc);\n };\n } else {\n error(inst, \"bad perform instruction -- assemble\");\n }\n}\n```\n\n```javascript\nThe function perform\n\tconstructs perform instructions. The\n\tselector\n\tperform_@action extracts\n\tthe action from a perform instruction.\n\n\t perform_perform_action\n make_perform\n gcd_machine_complete_example\n\nfunction perform(action) { return list(\"perform\", action); }\n\nfunction perform_action(perform_instruction) {\n return head(tail(perform_instruction));\n}\n```\n\nThe value of a\nconstant\nexpression may be needed for assignment to a register\n(make_assign_ef, above)\nor for input to an operation\n(make_@operation_@exp_@ef,\nbelow).\n\nThe following\nfunction\ngenerates execution\nfunctions\nto produce values for these expressions during the simulation:\n\n```javascript\nmake_primitive_exp\n lookup_label\n gcd_machine_complete_example\n\nfunction make_primitive_exp_ef(exp, machine, labels) {\n if (is_constant_exp(exp)) {\n const c = constant_exp_value(exp);\n return () => c;\n } else if (is_label_exp(exp)) {\n const insts = lookup_label(labels, label_exp_label(exp));\n return () => insts;\n } else if (is_register_exp(exp)) {\n const r = get_register(machine, register_exp_reg(exp));\n return () => get_contents(r);\n } else {\n error(exp, \"unknown expression type -- assemble\");\n }\n}\n```", + "token_count": 297, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": "Instructions and Their Execution Functions", + "chunk_index": 5, + "chunk_id": "Computing_with_Register_Machines_Instructions_and_Their_Execution_Functions_5" + }, + { + "content": "The following\nfunction\ngenerates execution\nfunctions\nto produce values for these expressions during the simulation:\n\n```javascript\nThe syntax of reg,\n\tlabel, and constant\n expressions is determined by the following constructor functions, along with\n corresponding predicates and selectors.\n\n\t is_register_exp0\n\t tagged_list\n\t gcd_machine_complete_example\n\nfunction reg(name) { return list(\"reg\", name); }\n\nfunction is_register_exp(exp) { return is_tagged_list(exp, \"reg\"); }\n\nfunction register_exp_reg(exp) { return head(tail(exp)); }\n\nfunction constant(value) { return list(\"constant\", value); }\n\nfunction is_constant_exp(exp) {\n return is_tagged_list(exp, \"constant\");\n}\n\nfunction constant_exp_value(exp) { return head(tail(exp)); }\n\nfunction label(name) { return list(\"label\", name); }\n\nfunction is_label_exp(exp) { return is_tagged_list(exp, \"label\"); }\n\nfunction label_exp_label(exp) { return head(tail(exp)); }\n```\n\nThe instructions\nmay include the application of a machine operation (specified by an\nconstant\nexpressions).\n\nThe following\nfunction\nproduces an execution\nfunction\nfor an operation expression a list containing the\noperation and operand expressions from the instruction:\n\n```javascript\nmake_operation_exp\n lookup_prim\n make_primitive_exp\n gcd_machine_complete_example\n\nfunction make_operation_exp_ef(exp, machine, labels, operations) {\n const op = lookup_prim(operation_exp_op(exp), operations);\n const afuns = map(e => make_primitive_exp_ef(e, machine, labels),\n operation_exp_operands(exp));\n return () => apply_in_underlying_javascript(\n op, map(f => f(), afuns));\n}\n```\n\n```javascript\nThe syntax of operation expressions is determined by\n\n\t is_register_exp\n\t is_register_exp0\n\t gcd_machine_complete_example\n\nfunction op(name) { return list(\"op\", name); }\n\nfunction is_operation_exp(exp) {\n return is_pair(exp) && is_tagged_list(head(exp), \"op\");\n}\n\nfunction operation_exp_op(op_exp) { return head(tail(head(op_exp))); }\n\nfunction operation_exp_operands(op_exp) { return tail(op_exp); }\n```\n\nObserve that the treatment of operation expressions is very much like the treatment of function applications by the analyze_application function in the evaluator of section\n\nin that we generate an execution function for each operand.\n\n```javascript\nAt simulation time, we call the operand\n\tfunctions\n\tand apply the JavaScript function\n```\n\nthat simulates the operation to the resulting values.", + "token_count": 273, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": "Instructions and Their Execution Functions", + "chunk_index": 6, + "chunk_id": "Computing_with_Register_Machines_Instructions_and_Their_Execution_Functions_6" + }, + { + "content": "that simulates the operation to the resulting values.\n\n```javascript\nWe make use of the function\n\tapply_in_underlying_javascript, as we did\n\tin apply_primitive_function in\n\tsection. This is needed to apply\n\top to all elements of the argument list\n\tafuns\n\tproduced by the first map,\n\tas if they were separate arguments to\n\top. Without this,\n\top would have been restricted to be a unary\n\tfunction.\n```\n\nThe simulation function is found by looking up the operation name in the operation table for the machine:\n\n```javascript\nlookup_prim\n gcd_machine_complete_example\n\nfunction lookup_prim(symbol, operations) {\n const val = assoc(symbol, operations);\n return is_undefined(val)\n ? error(symbol, \"unknown operation -- assemble\")\n : head(tail(val));\n}\n```", + "token_count": 105, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": "Instructions and Their Execution Functions", + "chunk_index": 7, + "chunk_id": "Computing_with_Register_Machines_Instructions_and_Their_Execution_Functions_7" + }, + { + "content": "Simulation is useful not only for verifying the correctness of a\nproposed machine design but also for measuring the machine s\nmeter that measures the number of stack operations used in a\ncomputation.\n\nTo do this, we modify our simulated stack to keep track\nof the number of times registers are saved on the stack and the\nmaximum depth reached by the stack, and add a message to the stack s\ninterface that prints the statistics, as shown below.\n\nWe also add an operation to the basic machine model to print the\nstack statistics, by initializing\nthe_ops\nin\nmake_new_machine\nto\n\n```javascript\nlist(list(\"initialize_stack\",\n () => stack(\"initialize\")),\n list(\"print_stack_statistics\",\n () => stack(\"print_statistics\")));\n```\n\nHere is the new version of make_stack:\n\n```javascript\nfunction make_stack() {\n let stack = null;\n let number_pushes = 0;\n let max_depth = 0;\n let current_depth = 0;\n function push(x) {\n stack = pair(x, stack);\n number_pushes = number_pushes + 1;\n current_depth = current_depth + 1;\n max_depth = math_max(current_depth, max_depth);\n return \"done\";\n }\n function pop() {\n if (is_null(stack)) {\n error(\"empty stack -- pop\");\n } else {\n const top = head(stack);\n stack = tail(stack);\n current_depth = current_depth - 1;\n return top;\n }\n }\n function initialize() {\n stack = null;\n number_pushes = 0;\n max_depth = 0;\n current_depth = 0;\n return \"done\";\n }\n function print_statistics() {\n display(\"total pushes = \" + stringify(number_pushes));\n display(\"maximum depth = \" + stringify(max_depth));\n }\n function dispatch(message) {\n return message === \"push\"\n ? push\n : message === \"pop\"\n ? pop()\n : message === \"initialize\"\n ? initialize()\n : message === \"print_statistics\"\n ? print_statistics()\n : error(message, \"unknown request -- stack\");\n }\n return dispatch;\n}\n```\n\nExercises through describe other useful monitoring and debugging features that can be added to the register-machine simulator.", + "token_count": 279, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": "Monitoring Machine Performance", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Monitoring_Machine_Performance_1" + }, + { + "content": "The technique of producing an execution\nfunction\nfor each instruction is just what we used in\nsection to speed\nup the evaluator by separating analysis from runtime execution.\n\nAs we\nsaw in chapter , much useful\nJavaScript\nexpressions could\nbe performed without knowing the actual values of\nnames.\n\nHere, analogously, much useful analysis of register-machine-language\nexpressions can be performed without knowing the actual contents of\nmachine registers.\n\nFor example, we can replace references to\nregisters by pointers to the register objects, and we can\nreplace references to labels by pointers to the place in the\ninstruction sequence that the label designates.\n\nBefore it can generate the instruction execution\nfunctions,\nthe assembler must know what all the labels refer to, so it begins by\nscanning the controller sequence to separate the labels from the\ninstructions.\n\nAs it scans the controller , it constructs both a list of\ninstructions and a table that associates each label with a pointer\ninto that list.\n\nThen the assembler augments the instruction list by\ninserting the execution\nfunction\nfor each instruction.\n\nThe\nfunction\nis the main entry to the assembler.\n\nIt takes the controller\nsequence and the\nmachine model as arguments and returns the instruction sequence to be stored\nin the model.\n\n```javascript\nThe function\n\tassemble\n```\n\ncalls\nextract_labels\nto build the initial instruction list and label table from the supplied\ncontroller.\n\nThe second argument\nto\nextract_labels\nis a\nfunction\nto be called to process these results: This\nfunction\nuses\nupdate_insts\nto generate the instruction execution\nfunctions\nand insert them into the instruction list, and returns the modified list.\n\n```javascript\nassemble\n update_insts\n extract_labels\n gcd_machine_complete_example\n\nfunction assemble(controller, machine) {\n return extract_labels(controller,\n (insts, labels) => {\n update_insts(insts, labels, machine);\n return insts;\n });\n}\n```", + "token_count": 286, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": "The Assembler", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_The_Assembler_1" + }, + { + "content": "The second argument\nto\nextract_labels\nis a\nfunction\nto be called to process these results: This\nfunction\nuses\nupdate_insts\nto generate the instruction execution\nfunctions\nand insert them into the instruction list, and returns the modified list.\n\n```javascript\nThe function extract_labels takes\n\t a list controller\n\t and a function receive\n\t as arguments. The function receive will\n\t be called with two values: (1)a list insts\n\t of instruction data structures, each containing an instruction from\n\t controller; and (2)a table called\n\t labels, which associates each label from\n\t controller with the position in the list\n\t insts that the label designates.\n```\n\n```javascript\nextract_labels\n make_label_entry\n gcd_machine_complete_example\n\nfunction extract_labels(controller, receive) {\n return is_null(controller)\n ? receive(null, null)\n : extract_labels(\n tail(controller),\n (insts, labels) => {\n const next_element = head(controller);\n return is_string(next_element)\n ? receive(insts,\n pair(make_label_entry(next_element,\n insts),\n labels))\n : receive(pair(make_inst(next_element),\n insts),\n labels);\n });\n}\n```\n\n```javascript\nThe function\n extract_labels\n```\n\nworks by sequentially scanning the elements of the controller and accumulating the string (and thus a label) an appropriate entry is added to the\n\n```javascript\nThe function\n update_insts\n```\n\nmodifies the instruction list, which initially contains only the controller instructions, to include the corresponding execution functions:\n\n```javascript\nupdate_insts\n get_register\n make_execution_function\n make_inst\n gcd_machine_complete_example\n\nfunction update_insts(insts, labels, machine) {\n const pc = get_register(machine, \"pc\");\n const flag = get_register(machine, \"flag\");\n const stack = machine(\"stack\");\n const ops = machine(\"operations\");\n return for_each(inst => set_inst_execution_fun(\n inst,\n make_execution_function(\n inst_controller_instruction(inst),\n labels, machine, pc,\n flag, stack, ops)),\n insts);\n}\n```\n\nThe machine instruction data structure simply pairs the\ncontroller\ninstruction with the corresponding execution\nfunction.\n\nThe execution\nfunction\nis not yet available when\nextract_labels\nconstructs the instruction, and is inserted later by\nupdate_insts.\n\n```javascript\nmake_inst\n gcd_machine_complete_example\n\nfunction make_inst(inst_controller_instruction) {\n return pair(inst_controller_instruction, null);\n}\nfunction inst_controller_instruction(inst) {\n return head(inst);\n}\nfunction inst_execution_fun(inst) {\n return tail(inst);\n}\nfunction set_inst_execution_fun(inst, fun) {\n set_tail(inst, fun);\n}\n```", + "token_count": 293, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": "The Assembler", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_The_Assembler_2" + }, + { + "content": "The execution\nfunction\nis not yet available when\nextract_labels\nconstructs the instruction, and is inserted later by\nupdate_insts.\n\nThe controller instruction is not used by our simulator, but is handy to keep around for debugging (see exercise ).\n\nElements of the label table are pairs:\n\n```javascript\nmake_label_entry\n gcd_machine_complete_example\n\nfunction make_label_entry(label_name, insts) {\n return pair(label_name, insts);\n}\n```\n\nEntries will be looked up in the table with\n\n```javascript\nlookup_label\n gcd_machine_complete_example\n\nfunction lookup_label(labels, label_name) {\n const val = assoc(label_name, labels);\n return is_undefined(val)\n ? error(label_name, \"undefined label -- assemble\")\n : tail(val);\n}\n```", + "token_count": 90, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": "The Assembler", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_The_Assembler_3" + }, + { + "content": "The machine model generated by\nmake_machine\nis represented as a\nfunction\nwith local state using the message-passing techniques\ndeveloped in chapter.\n\nTo build this model,\nmake_machine\nbegins by calling the\nfunction\nmake_new_machine\nto construct\nthe parts of the machine model that are common to all register\nmachines.\n\nThis basic machine model constructed by\nmake_new_machine\nis essentially a container for some registers and a stack, together with an\nexecution mechanism that processes the controller instructions one by one.\n\n```javascript\nThe function\n\tmake_machine\n```\n\nthen extends this basic model (by sending it\nmessages) to include the registers, operations, and controller of the\nparticular machine being defined.\n\nFirst it allocates a register in\nthe new machine for each of the supplied register names and installs\nthe designated operations in the machine.\n\nThen it uses an\nassembler (described below in\nsection ) to transform the controller list\ninto instructions for the new machine and installs these as the\nmachine s instruction sequence.\n\n```javascript\nThe function\n\tmake_machine\n```\n\nreturns as its value the modified machine model.\n\n```javascript\ngcd_machine_complete_example\n make_machine\n start\n\nconst gcd_machine =\n make_machine(\n list(\"a\", \"b\", \"t\"),\n list(list(\"rem\", (a, b) => a % b),\n list(\"=\", (a, b) => a === b)),\n list(\n \"test_b\",\n test(list(op(\"=\"), reg(\"b\"), constant(0))),\n branch(label(\"gcd_done\")),\n assign(\"t\", list(op(\"rem\"), reg(\"a\"), reg(\"b\"))),\n assign(\"a\", reg(\"b\")),\n assign(\"b\", reg(\"t\")),\n go_to(label(\"test_b\")),\n \"gcd_done\"));\nset_register_contents(gcd_machine, \"a\", 206);\nset_register_contents(gcd_machine, \"b\", 40);\nstart(gcd_machine);\nget_register_contents(gcd_machine, \"a\");\n```\n\n```javascript\nmake_machine\n assemble\n make_new_machine\n gcd_machine_complete_example\n\nfunction make_machine(register_names, ops, controller) {\n const machine = make_new_machine();\n for_each(register_name =>\n machine(\"allocate_register\")(register_name),\n register_names);\n machine(\"install_operations\")(ops);\n machine(\"install_instruction_sequence\")\n (assemble(controller, machine));\n return machine;\n}\n```\n\nWe will represent a register as a\nfunction\nwith local state, as in\nchapter.\n\nThe\nfunction\nmake_register\ncreates a register that\nholds a value that can be accessed or changed:", + "token_count": 278, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": "The Machine Model", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_The_Machine_Model_1" + }, + { + "content": "The\nfunction\nmake_register\ncreates a register that\nholds a value that can be accessed or changed:\n\n```javascript\nmake_register\n gcd_machine_complete_example\n\nfunction make_register(name) {\n let contents = \"*unassigned*\";\n function dispatch(message) {\n return message === \"get\"\n ? contents\n : message === \"set\"\n ? value => { contents = value; }\n : error(message, \"unknown request -- make_register\");\n }\n return dispatch;\n}\n```\n\nThe following functions are used to access registers:\n\n```javascript\nget_contents\n gcd_machine_complete_example\n\nfunction get_contents(register) {\n return register(\"get\");\n}\nfunction set_contents(register, value) {\n return register(\"set\")(value);\n}\n```\n\nWe can also represent a stack as a\nfunction\nwith local state.\n\nThe\nfunction\nmake_@stack\ncreates a stack whose local state consists\nof a list of the items on the stack.\n\nA stack accepts requests to\n\n```javascript\nmake_stack\n gcd_machine_complete_example\n\nfunction make_stack() {\n let stack = null;\n let frame = null;\n function push_marker() {\n frame = pair(stack, frame);\n return \"done\";\n }\n function pop_marker() {\n stack = head(frame);\n frame = tail(frame);\n return \"done\";\n }\n function push(x) {\n stack = pair(x, stack);\n return \"done\";\n }\n function pop() {\n if (is_null(stack)) {\n error(\"empty stack -- pop\");\n } else {\n const top = head(stack);\n stack = tail(stack);\n return top;\n }\n }\n function initialize() {\n stack = null;\n return \"done\";\n }\n function dispatch(message) {\n return message === \"push\"\n ? push\n : message === \"pop\"\n ? pop()\n : message === \"push_marker\"\n ? push_marker()\n : message === \"pop_marker\"\n ? pop_marker()\n : message === \"initialize\"\n ? initialize()\n : error(message, \"unknown request -- stack\");\n }\n return dispatch;\n}\n\nfunction make_push_marker_to_stack_ef(machine, stack, pc) {\n return () => {\n push_marker(stack);\n advance_pc(pc);\n };\n}\nfunction make_revert_stack_to_marker_ef(machine, stack, pc) {\n return () => {\n pop_marker(stack);\n advance_pc(pc);\n };\n}\n\nfunction push_marker_to_stack() { return list(\"push_marker_to_stack\"); }\nfunction revert_stack_to_marker() { return list(\"revert_stack_to_marker\"); }\n\nfunction pop_marker(stack) {\n return stack(\"pop_marker\");\n}\nfunction push_marker(stack) {\n return stack(\"push_marker\");\n}\n```", + "token_count": 292, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": "The Machine Model", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_The_Machine_Model_2" + }, + { + "content": "A stack accepts requests to\n\n```javascript\nfunction make_stack() {\n let stack = null;\n function push(x) {\n stack = pair(x, stack);\n return \"done\";\n }\n function pop() {\n if (is_null(stack)) {\n error(\"empty stack -- pop\");\n } else {\n const top = head(stack);\n stack = tail(stack);\n return top;\n }\n }\n function initialize() {\n stack = null;\n return \"done\";\n }\n function dispatch(message) {\n return message === \"push\"\n ? push\n : message === \"pop\"\n ? pop()\n : message === \"initialize\"\n ? initialize()\n : error(message, \"unknown request -- stack\");\n }\n return dispatch;\n}\n```\n\nThe following functions are used to access stacks:\n\n```javascript\npop\n gcd_machine_complete_example\n\nfunction pop(stack) {\n return stack(\"pop\");\n}\nfunction push(stack, value) {\n return stack(\"push\")(value);\n}\n```\n\nThe\nmake_new_machine\nfunction,\nshown in figure , constructs an\nobject whose local state consists of a stack, an initially empty instruction\nsequence, a list of operations that initially contains an operation to\nregister table that initially contains two\nregisters, named\nprogram counter ).\n\nThe internal\nfunction\nallocate_register\nadds new entries to the register table, and the internal\nfunction\nlookup_register\nlooks up registers in the table.\n\nThe Our test instructions set the contents of Our branch instructions decide whether or not to branch by examining the contents of\n\nThe\nfunction\nfunction\nof no arguments, called the\ninstruction execution function,\nsuch that calling this\nfunction\nsimulates executing the instruction.\n\nAs the simulation runs,\nThe function execute\ngets that instruction, executes it by calling the instruction execution\nfunction,\nand repeats this cycle until there are no more instructions to execute\n(i.e., until\n\nAs part of its operation, each instruction execution function modifies\n\n```javascript\nThe instructions\n\tbranch\n\tand\n\tgo_to\n```\n\nchange function changes the contents of\n\n```javascript\nThe function\n\tmake_new_machine\n```\n\nreturns a\ndispatch\nfunction\nthat implements message-passing access to the internal state.\n\nNotice that\nstarting the machine is accomplished by setting", + "token_count": 301, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": "The Machine Model", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_The_Machine_Model_3" + }, + { + "content": "Notice that\nstarting the machine is accomplished by setting\n\nFor convenience, we provide an alternate interface to a machine s functions to set and examine register contents, as specified at the beginning of section\n\n:\n\n```javascript\nstart\n gcd_machine_complete_example\n\nfunction start(machine) {\n return machine(\"start\");\n}\nfunction get_register_contents(machine, register_name) {\n return get_contents(get_register(machine, register_name));\n}\nfunction set_register_contents(machine, register_name, value) {\n set_contents(get_register(machine, register_name), value);\n return \"done\";\n}\n```\n\nThese functions (and many functions in sections and ) use the following to look up the register with a given name in a given machine:\n\n```javascript\nget_register\n gcd_machine_complete_example\n\nfunction get_register(machine, reg_name) {\n return machine(\"get_register\")(reg_name);\n}\n```", + "token_count": 100, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "A Register-Machine Simulator", + "subsection": "The Machine Model", + "chunk_index": 4, + "chunk_id": "Computing_with_Register_Machines_The_Machine_Model_4" + }, + { + "content": "The explicit-control evaluator of\nsection is a\nregister machine whose controller interprets\nJavaScript\nprograms.\n\nIn this\nsection we will see how to run\nJavaScript\nprograms on a register machine whose controller is not a\nJavaScript\ninterpreter.\n\nThe explicit-control evaluator machine is\nit\ncan carry out any computational process that can be described in\nJavaScript.\n\nThe\nevaluator s controller orchestrates the use of its data\npaths to perform the desired computation.\n\nThus, the\nevaluator s data paths are universal: They are sufficient\nto perform any computation we desire, given an appropriate\ncontroller.\n\nCommercial\nnative language of the machine, or simply\nmachine language.\n\nPrograms written in machine language are\nsequences of instructions that use the machine s data paths.\n\nFor example, the\ns instruction sequence\ncan be thought of as a machine-language program for a general-purpose\ncomputer rather than as the controller for a specialized interpreter\nmachine.\n\nThere are two common strategies for bridging the gap between\nhigher-level languages and register-machine languages.\n\nThe explicit-control evaluator illustrates the\nstrategy of interpretation.\n\nAn interpreter written in the native\nlanguage of a machine configures the machine to execute programs\nwritten in a language (called the\nsource language ) that may\ndiffer from the native language of the machine performing the\nevaluation.\n\nThe primitive\nfunctions\nof the source language are implemented as a library of subroutines written\nin the native language of the given machine.\n\nA program to be interpreted\n(called the\nsource program ) is represented as a data structure.\n\nThe interpreter\ntraverses this data structure, analyzing the source program.\n\nAs it\ndoes so, it simulates the intended behavior of the source program by\ncalling appropriate primitive subroutines from the library.\n\nIn this section, we explore the alternative strategy of\ncompilation.", + "token_count": 286, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Compilation_1" + }, + { + "content": "In this section, we explore the alternative strategy of\ncompilation.\n\nA compiler for a given source language and machine\ntranslates a source program into an equivalent program (called the\nobject program ) written in the machine s native language.\n\nThe compiler that we implement in this section translates programs written in\nJavaScript\ninto sequences of instructions to be executed using the explicit-control\nevaluator machine s data paths.\n\nCompared with interpretation, compilation can provide a great increase\nin the efficiency of program execution, as we will explain below in\nthe overview of the compiler.\n\nOn the other hand, an interpreter\nprovides a more powerful environment for interactive program\ndevelopment and debugging, because the source program being executed\nis available at run time to be examined and modified.\n\nIn addition,\nbecause the entire library of primitives is present, new programs can\nbe constructed and added to the system during debugging.\n\nIn view of the complementary advantages of compilation and\ninterpretation, modern\nprogram-development environments\npursue a mixed\nstrategy.\n\nThese systems\nare generally organized so that interpreted\nfunctions\nand compiled\nfunctions\ncan call each other.\n\nThis enables a programmer to compile those parts of a\nprogram that are assumed to be debugged, thus gaining the efficiency\nadvantage of compilation, while retaining the interpretive mode of execution\nfor those parts of the program that are in the flux of interactive\ndevelopment and\ndebugging.\n\nIn section , after\nwe have implemented the compiler, we will show how to interface it\nwith our interpreter to produce an integrated\ninterpreter-compiler\nsystem.\n\nOur compiler is much like our interpreter, both in its structure and in\nthe function it performs.\n\nAccordingly, the mechanisms used by the\ncompiler for analyzing\ncomponents\nwill be similar to those used by\nthe interpreter.", + "token_count": 289, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Compilation_2" + }, + { + "content": "Accordingly, the mechanisms used by the\ncompiler for analyzing\ncomponents\nwill be similar to those used by\nthe interpreter.\n\nMoreover, to make it easy to interface compiled and\ninterpreted code, we will design the compiler to generate code that\nobeys the same conventions of\nfunction\nto be applied will be in\nfun,\nfunctions\nwill return their answers in\nfunction\nshould return will be kept in\n\nThis description suggests a strategy for implementing a rudimentary\ncompiler: We traverse the\ncomponent\nin the same way the\ninterpreter does.\n\nWhen we encounter a register instruction that the\ninterpreter would perform in evaluating the\ncomponent,\nwe do not\nexecute the instruction but instead accumulate it into a sequence.\n\nThe\nresulting sequence of instructions will be the object code.\n\nObserve\nthe\na componentfor example,\nf(96, 22)it\nperforms the work of classifying the\ncomponent\n(discovering that this is a\nfunction\napplication) and\ntesting for the end of the\n\n```javascript\nlist of argument expressions\n\t(discovering that there are two argument expressions).\n```\n\nWith a\ncompiler, the\ncomponent\nis analyzed only once, when the\ninstruction sequence is generated at compile time.\n\nThe object code\nproduced by the compiler contains only the instructions that evaluate\nthe\nfunction expression and the two argument expressions,\nassemble the argument list, and apply the\nfunction (in\nto the arguments (in\n\nThis is the same kind of optimization we implemented in the.\n\nBut there are further opportunities to gain efficiency in compiled code.\n\nAs the interpreter runs, it follows a process that must be applicable\nto any\ncomponent\nin the language.\n\nIn contrast, a given segment of\ncompiled code is meant to execute some particular\ncomponent.\n\nThis can make a big difference, for example in the use of the\nstack to save registers.\n\nWhen the interpreter evaluates\na component,\nit must be prepared for any contingency.", + "token_count": 302, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": null, + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_Compilation_3" + }, + { + "content": "When the interpreter evaluates\na component,\nit must be prepared for any contingency.\n\nBefore evaluating a\nsubcomponent,\nthe interpreter saves all registers that will be needed later, because the\nsubcomponent\nmight require an arbitrary evaluation.\n\nA compiler, on the other hand, can exploit the structure of the particular\ncomponent\nit is processing to generate code that avoids\nunnecessary stack operations.\n\nAs a case in point, consider the\napplication f(96, 22).\n\nBefore the interpreter evaluates the\nfunction expression of the application,\nit prepares\nfor this evaluation by saving the registers containing the\nargument expressions\nand the environment, whose values will be needed later.\n\nThe interpreter then\nevaluates the\nfunction expression\nto obtain the result in\nHowever, in the particular expression we\nare dealing with, the\nfunction expression\nis the\nname\nlookup_symbol_value,\nwhich does not alter any registers.\n\nThe compiler that we implement in\nthis section will take advantage of this fact and generate code that\nevaluates the\nfunction expression\nusing the instruction\n\n```javascript\nassign(\"fun\",\n list(op(\"lookup_symbol_value\"), constant(\"f\"), reg(\"env\")))\n```\n\n```javascript\nwhere the argument to lookup_symbol_value\n\tis extracted at compile time from the parser's representation of f(96, 22).\n```\n\nThis code not only avoids the unnecessary saves and restores but also assigns the value of the lookup directly to whereas the interpreter would obtain\n\nthe result in\n\nA compiler can also optimize access to the environment.\n\nHaving\nanalyzed the code, the compiler can\nknow in which frame\nthe value of a particular name\nwill be located and access that frame directly,\nrather than performing the\nlookup_@symbol_@value\nsearch.\n\nWe will discuss how to implement such\nlexical addressing\nin\nsection.\n\nUntil then, however,\nwe will focus on the kind of register and stack optimizations described\nabove.", + "token_count": 281, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": null, + "chunk_index": 4, + "chunk_id": "Computing_with_Register_Machines_Compilation_4" + }, + { + "content": "Until then, however,\nwe will focus on the kind of register and stack optimizations described\nabove.\n\nThere are many other optimizations that can be performed by a\ncompiler, such as coding primitive operations in line instead\nof using a general ); but we will not emphasize these\nhere.\n\nOur main goal in this section is to illustrate the compilation process\nin a simplified (but still interesting) context.", + "token_count": 67, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": null, + "chunk_index": 5, + "chunk_id": "Computing_with_Register_Machines_Compilation_5" + }, + { + "content": "To design a register machine, we must design its data paths\n(registers and operations) and the controller that sequences\nthese operations.\n\nTo illustrate the design of a simple register\nmachine, let us examine Euclid s Algorithm, which is used to compute\n,\ns Algorithm can be\ncarried out by an iterative process, as specified by the following\nfunction:\n\n```javascript\ngcd_example\n\nfunction gcd(a, b) {\n return b === 0 ? a : gcd(b, a % b);\n}\n```\n\nA machine to carry out this algorithm must keep track of two numbers,\n$a$ and $b$ , so let us\nassume that these numbers are stored in two registers with those names.\n\nThe\nbasic operations required are testing whether the contents of register\ntemporary register, which we call\n\nWe can illustrate the registers and operations required for this\nmachine by using the.\n\nIn this\ndiagram, the registers ( a buttondrawn as $\\otimes$ behind the\nhead, pointing from the source of data to the register.\n\nWhen pushed, the button allows\nthe value at the source to flow into the designated register.\n\nThe label next to each button is the name we will use to refer to the\nbutton.\n\nThe names are arbitrary, and can be chosen to have mnemonic value\n(for example,\n\nAn operation that computes a value from constants and the contents\nof registers is represented in a data-path diagram by a trapezoid\ncontaining a name for the operation.\n\nFor example, the box marked\nrepresents an operation that\ncomputes the remainder of the contents of the registers\ns output value\nto registers.\n\nA test is represented by a circle containing a name for the\ntest.", + "token_count": 272, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Designing_Register_Machines_1" + }, + { + "content": "A test is represented by a circle containing a name for the\ntest.\n\nFor example, our GCD machine has an operation that tests whether the\ncontents of register $\\otimes$ buttons as switches, the data-path diagram\nis very like the wiring diagram for a machine that could be constructed\nfrom electrical components.\n\nData paths for a GCD machine.\n\nIn order for the data paths to actually compute GCDs, the buttons must\nbe pushed in the correct sequence.\n\nWe will describe this sequence in\nterms of a.\n\nThe elements of the\ncontroller diagram indicate how the data-path components should be operated.\n\nThe rectangular boxes in the controller diagram identify data-path buttons\nto be pushed, and the arrows describe the sequencing from one step to the\nnext.\n\nThe diamond in the diagram represents a decision.\n\nOne of the two\nsequencing arrows will be followed, depending on the value of the data-path\ntest identified in the diamond.\n\nWe can interpret the controller in terms\nof a physical analogy: Think of the diagram as a maze in which a marble is\nrolling.\n\nWhen the marble rolls into a box, it pushes the data-path button\nthat is named by the box.\n\nWhen the marble rolls into a decision node (such\nas the test for\n$\\, =0$ ), it leaves\nthe node on the path determined by the result of the indicated test.\n\nTaken together, the data paths and the controller completely describe\na machine for computing GCDs.\n\nWe start the controller (the rolling\nmarble) at the place marked", + "token_count": 252, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Designing_Register_Machines_2" + }, + { + "content": "In section , we will show how to implement a\nJavaScript\nevaluator as a register machine.\n\nIn order to simplify the discussion, we\nwill assume that our register machines can be equipped with a\nlist-structured memory , in which the basic operations for\nmanipulating list-structured data are primitive.\n\nPostulating the existence\nof such a memory is a useful abstraction when one is focusing on the\nmechanisms of control in\nan\ninterpreter, but this does not reflect a realistic view of the actual\nprimitive data operations of contemporary computers.\n\nTo obtain a more\ncomplete picture of how\n\n```javascript\nsystems can support list-structured memory\n efficiently,\n```\n\nwe must investigate how list structure can be represented in a way that is compatible with conventional computer memories.\n\nThere are two considerations in implementing list structure.\n\nThe first is\npurely an issue of representation: how to represent the\nbox-and-pointer structure of\npairs, using only the storage and addressing capabilities of typical computer\nmemories.\n\nThe second issue concerns the management of memory as a\ncomputation proceeds.\n\nThe operation of a\nJavaScript\nsystem depends crucially on the ability to\ncontinually create new data objects.\n\nThese include objects that are\nexplicitly created by the\nJavaScript\nfunctions\nbeing interpreted as well as structures created by the interpreter itself,\nsuch as environments and argument lists.\n\nAlthough the constant creation of\nnew data objects would pose no problem on a computer with an infinite amount\nof rapidly addressable memory, computer memories are available only in\nfinite sizes (more s the pity).\n\nJavaScript\nthus provide an\nautomatic storage allocation facility to\nsupport the illusion of an infinite memory.\n\nWhen a data object is no longer\nneeded, the memory allocated to it is automatically recycled and used to\nconstruct new data objects.\n\nThere are various techniques for providing such\nautomatic storage allocation.", + "token_count": 299, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Storage_Allocation_and_Garbage_Collection_1" + }, + { + "content": "There are various techniques for providing such\nautomatic storage allocation.\n\nThe method we shall discuss in this section\nis called garbage collection.", + "token_count": 22, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Storage_Allocation_and_Garbage_Collection_2" + }, + { + "content": "The representation method outlined in\nsection solves the problem of\nimplementing list structure, provided that we have an infinite amount of\nmemory.\n\nWith a real computer we will eventually run out of free space in\nwhich to construct new pairs. they are\ngarbage.\n\nFor instance, the computation\n\n```javascript\naccumulate((x, y) => x + y,\n 0,\n filter(is_odd, enumerate_interval(0, n)))\n```\n\nconstructs two lists: the enumeration and the result of filtering\nthe enumeration.\n\nWhen the accumulation is complete, these lists are\nno longer needed, and the allocated memory can be reclaimed.\n\nIf we\ncan arrange to collect all the garbage periodically, and if this turns\nout to recycle memory at about the same rate at which we construct new\npairs, we will have preserved the illusion that there is an infinite\namount of memory.\n\nIn order to recycle pairs, we must have a way to determine which\nallocated pairs are not needed (in the sense that their contents can\nno longer influence the future of the computation).\n\nThe method we\nshall examine for accomplishing this is known as garbage\ncollection.\n\nGarbage collection is based on the observation that, at\nany moment in\nan interpretation based on list-structured memory,\nthe only objects that can\naffect the future of the computation are those that can be reached by\nsome succession of\nhead\nand\ntail\noperations starting from the pointers that are currently in the machine\nregisters.\n\nThere are many ways to perform garbage collection.\n\nThe method we\nshall examine here is called\nstop-and-copy.\n\nThe basic idea is to divide memory into two\nhalves: working memory and free memory.\n\nWhen\npair\nconstructs pairs, it allocates these in working memory.\n\nWhen working memory\nis full, we perform garbage collection by locating all the useful pairs in\nworking memory and copying these into consecutive locations in free memory.", + "token_count": 301, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Maintaining the Illusion of Infinite Memory", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_1" + }, + { + "content": "When working memory\nis full, we perform garbage collection by locating all the useful pairs in\nworking memory and copying these into consecutive locations in free memory.\n\n(The useful pairs are located by tracing all the\nhead\nand\ntail\npointers, starting with the machine registers.) Since we do not copy the\ngarbage, there will presumably be additional free memory that we can\nuse to allocate new pairs.\n\nIn addition, nothing in the working memory\nis needed, since all the useful pairs in it have been copied.\n\nThus,\nif we interchange the roles of working memory and free memory, we can\ncontinue processing; new pairs will be allocated in the new working\nmemory (which was the old free memory).\n\nWhen this is full, we can\ncopy the useful pairs into the new free memory (which was the old\nworking memory).\n\nWe now use our register-machine language to describe the stop-and-copy\nalgorithm in more detail.\n\nWe will assume that there is a register\ncalled\nthe_heads\nand\nthe_tails,\nand the free memory is in registers called\nnew_heads\nand\nnew_tails.\n\nGarbage collection is triggered when we exhaust the free cells in the\ncurrent working memory, that is, when a\npair\noperation attempts to increment the new pairs will be constructed in the new memory,\nbeginning at the place indicated by\nFigure\nshows the arrangement of memory just before and just after garbage\ncollection.\n\nReconfiguration of memory by the garbage-collection process.\n\nThe state of the garbage-collection process is controlled by\nmaintaining two pointers:\nhead\nposition, we place a special tag that signals that this is an already-moved\nobject.\n\n(Such an object is traditionally called a\nbroken heart.)\ntail\nposition we place a\nforwarding address that points at the location to which the object\nhas been moved.", + "token_count": 291, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Maintaining the Illusion of Infinite Memory", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_2" + }, + { + "content": "(Such an object is traditionally called a\nbroken heart.)\ntail\nposition we place a\nforwarding address that points at the location to which the object\nhas been moved.\n\nAfter relocating the root, the garbage collector enters its basic\ncycle.\n\nAt each step in the algorithm, the\nhead\nand\ntail\npointers still refer to objects in the old memory.\n\nThese objects are each\nrelocated, and the\nhead\npointer of the pair we are scanning) we check to see if the object has\nalready been moved (as indicated by the presence of a broken-heart tag\nin the\nhead\nposition of the object).\n\nIf the object has not\nalready been moved, we copy it to the place indicated by\ns old location, and update the pointer to the object (in this\nexample, the\nhead\npointer of the pair we are scanning) to point\nto the new location.\n\nIf the object has already been moved, its\nforwarding address (found in the\ntail\nposition of the broken heart) is substituted for the pointer in the pair\nbeing scanned.\n\nEventually, all accessible objects will have been moved and\nscanned, at which point the\n\nWe can specify the stop-and-copy algorithm as a sequence of instructions for\na register machine.\n\nThe basic step of relocating an object is accomplished\nby a subroutine called\nrelocate_old_result_in_new.\n\nThis subroutine gets its argument, a pointer to the object to be relocated,\nfrom a register named\nrelocate_continue.\n\nTo begin garbage collection, we invoke this subroutine to relocate the\n\n```javascript\nbegin_garbage_collection\n\n\"begin_garbage_collection\",\n assign(\"free\", constant(0)),\n assign(\"scan\", constant(0)),\n assign(\"old\", reg(\"root\")),\n assign(\"relocate_continue\", label(\"reassign_root\")),\n go_to(label(\"relocate_old_result_in_new\")),\n\"reassign_root\",\n assign(\"root\", reg(\"new\")),\n go_to(label(\"gc_loop\")),\n```\n\nIn the main loop of the garbage collector we must determine whether\nthere are any more objects to be scanned.\n\nWe do this by testing\nwhether the\ngc_flip,\nwhich cleans things up so that we can continue the interrupted computation.", + "token_count": 302, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Maintaining the Illusion of Infinite Memory", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_3" + }, + { + "content": "We do this by testing\nwhether the\ngc_flip,\nwhich cleans things up so that we can continue the interrupted computation.\n\nIf there are still pairs to be scanned, we call the relocate subroutine to\nrelocate the\nhead\nof the next pair (by placing the\nhead\npointer in\nrelocate_continue\nregister is set up so that the subroutine will return to update the\nhead\npointer.\n\n```javascript\ngc_loop\n\n\"gc_loop\",\n test(list(op(\"===\"), reg(\"scan\"), reg(\"free\"))),\n branch(label(\"gc_flip\")),\n assign(\"old\", list(op(\"vector_ref\"), reg(\"new_heads\"), reg(\"scan\"))),\n assign(\"relocate_continue\", label(\"update_head\")),\n go_to(label(\"relocate_old_result_in_new\")),\n```\n\nAt\nupdate_head,\nwe modify the\nhead\npointer of the pair being scanned, then proceed to relocate the\ntail\nof the pair.\n\nWe return to\nupdate_tail\nwhen that relocation has been accomplished.\n\nAfter relocating and updating\nthe\ntail,\nwe are finished scanning that pair, so we continue with the main loop.\n\n```javascript\nupdate_head\n\n\"update_head\",\n perform(list(op(\"vector_set\"),\n reg(\"new_heads\"), reg(\"scan\"), reg(\"new\"))),\n assign(\"old\", list(op(\"vector_ref\"),\n reg(\"new_tails\"), reg(\"scan\"))),\n assign(\"relocate_continue\", label(\"update_tail\")),\n go_to(label(\"relocate_old_result_in_new\")),\n\n\"update_tail\",\n perform(list(op(\"vector_set\"),\n reg(\"new_tails\"), reg(\"scan\"), reg(\"new\"))),\n assign(\"scan\", list(op(\"+\"), reg(\"scan\"), constant(1))),\n go_to(label(\"gc_loop\")),\n```\n\nThe subroutine\nrelocate_old_result_in_new\nrelocates objects as follows: If the object to be relocated (pointed at by\nhead\nis the number 4.\n\nIf we represent the\nhead\nby , then we want the\nrelocated\nhead\npointer to still be\nhead\nposition of the pair to be relocated contains a broken-heart tag, then the\npair has in fact already been moved, so we retrieve the forwarding address\n(from the\ntail\nposition of the broken heart) and return this in\n\n```javascript\nThe subroutine\n relocate_old_result_in_new\n```\n\nuses a register oldht to hold the head or the tail of the object pointed at by", + "token_count": 252, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Maintaining the Illusion of Infinite Memory", + "chunk_index": 4, + "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_4" + }, + { + "content": "uses a register oldht to hold the head or the tail of the object pointed at by\n\n```javascript\nrelocate_old_result_in_new\n\n\"relocate_old_result_in_new\",\n test(list(op(\"is_pointer_to_pair\"), reg(\"old\"))),\n branch(label(\"pair\")),\n assign(\"new\", reg(\"old\")),\n go_to(reg(\"relocate_continue\")),\n\"pair\",\n assign(\"oldht\", list(op(\"vector_ref\"),\n reg(\"the_heads\"), reg(\"old\"))),\n test(list(op(\"is_broken_heart\"), reg(\"oldht\"))),\n branch(label(\"already_moved\")),\n assign(\"new\", reg(\"free\")), // new location for pair\n // Update $\\texttt{free}$ pointer\n assign(\"free\", list(op(\"+\"), reg(\"free\"), constant(1))),\n // Copy the head and tail to new memory\n perform(list(op(\"vector_set\"),\n reg(\"new_heads\"), reg(\"new\"),\n reg(\"oldht\"))),\n assign(\"oldht\", list(op(\"vector_ref\"),\n reg(\"the_tails\"), reg(\"old\"))),\n perform(list(op(\"vector_set\"),\n reg(\"new_tails\"), reg(\"new\"),\n reg(\"oldht\"))),\n // Construct the broken heart\n perform(list(op(\"vector_set\"),\n reg(\"the_heads\"), reg(\"old\"),\n constant(\"broken_heart\"))),\n perform(list(op(\"vector_set\"),\n reg(\"the_tails\"), reg(\"old\"),\n reg(\"new\"))),\n go_to(reg(\"relocate_continue\")),\n\"already_moved\",\n assign(\"new\", list(op(\"vector_ref\"),\n reg(\"the_tails\"), reg(\"old\"))),\n go_to(reg(\"relocate_continue\")),\n```\n\nAt the very end of the garbage collection process, we interchange the\nrole of old and new memories by interchanging pointers: interchanging\nthe_heads\nwith\nnew_heads,\nand\nthe_tails\nwith\nnew_tails.\n\nWe will then be ready to perform another garbage\ncollection the next time memory runs out.", + "token_count": 136, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Maintaining the Illusion of Infinite Memory", + "chunk_index": 5, + "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_5" + }, + { + "content": "We will then be ready to perform another garbage\ncollection the next time memory runs out.\n\n```javascript\ntesting_5_3_2\n tagged_list\n make_go_to\n pop\n lookup1\n go_to_go_to_dest\n make_register\n\n// TYPED POINTERS\n\nconst NUMBER_TYPE = \"number\";\nconst BOOL_TYPE = \"bool\";\nconst STRING_TYPE = \"string\";\nconst PTR_TYPE = \"ptr\";\nconst PROG_TYPE = \"prog\";\nconst NULL_TYPE = \"null\";\nconst UNDEFINED_TYPE = \"undefined\";\nconst NO_VALUE_YET_TYPE = \"*unassigned*\";\nconst BROKEN_HEART_TYPE = \"broken_heart\";\n\nfunction make_ptr_ptr(idx) {\n return pair(PTR_TYPE, idx);\n}\n\nfunction make_null_ptr() {\n return pair(NULL_TYPE, null);\n}\n\nfunction make_no_value_yet_ptr() {\n return pair(NO_VALUE_YET_TYPE, null);\n}\n\nfunction make_prog_ptr(idx) {\n return pair(PROG_TYPE, idx);\n}\n\nfunction make_broken_heart_ptr() {\n return pair(BROKEN_HEART_TYPE, null);\n}\n\nfunction get_elem_type(elem) {\n return is_number(elem) ? NUMBER_TYPE :\n is_boolean(elem) ? BOOL_TYPE :\n is_string(elem) ? STRING_TYPE :\n is_null(elem) ? NULL_TYPE :\n is_undefined(elem) ? UNDEFINED_TYPE :\n error(elem, \"invalid typed elem\");\n}\n\nfunction wrap_ptr(elem) {\n return pair(get_elem_type(elem), elem);\n}\n\nfunction unwrap_ptr(ptr) {\n return tail(ptr);\n}\n\nfunction is_ptr(ptr) {\n return is_pair(ptr) &&\n !is_pair(head(ptr)) &&\n !is_pair(tail(ptr)) &&\n (head(ptr) === NUMBER_TYPE ||\n head(ptr) === BOOL_TYPE ||\n head(ptr) === STRING_TYPE ||\n head(ptr) === PTR_TYPE ||\n head(ptr) === NULL_TYPE ||\n head(ptr) === UNDEFINED_TYPE ||\n head(ptr) === PROG_TYPE ||\n head(ptr) === NO_VALUE_YET_TYPE ||\n head(ptr) === BROKEN_HEART_TYPE);\n}\n\nfunction is_number_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === NUMBER_TYPE;\n}\n\nfunction is_bool_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === BOOL_TYPE;\n}\n\nfunction is_string_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === STRING_TYPE;\n}\n\nfunction is_ptr_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === PTR_TYPE;\n}\n\nfunction is_null_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === NULL_TYPE;\n}\n\nfunction is_undefined_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === UNDEFINED_TYPE;\n}\n\nfunction is_prog_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === PROG_TYPE;\n}\n\nfunction is_no_value_yet_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === NO_VALUE_YET_TYPE;\n}\n\nfunction is_broken_heart_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === BROKEN_HEART_TYPE;\n}\n\n// Primitive functions and constants\n\nconst primitive_function_names_arities = list(\n pair(\"display\", 1),\n pair(\"error\", 1),\n pair(\"+\", 2),\n pair(\"-\", 2),\n pair(\"*\", 2),\n pair(\"/\", 2),\n pair(\"%\", 2),\n pair(\"===\", 2),\n pair(\"!==\", 2),\n pair(\"<\", 2),\n pair(\"<=\", 2),\n pair(\">\", 2),\n pair(\">=\", 2),\n pair(\"!\", 1),\n pair(\"||\", 2),\n pair(\"&&\", 2)\n);\n\nconst primitive_constants = list(\n list(\"undefined\", undefined),\n list(\"math_PI\" , math_PI)\n );\n\nfunction make_primitive_function(impl) {\n return list(\"primitive\", impl);\n}\n\nfunction setup_environment() {\n const primitive_function_names =\n map(head, primitive_function_names_arities);\n const primitive_function_values =\n map(name => pair(make_primitive_function(name), false),\n primitive_function_names);\n const primitive_constant_names =\n map(head, primitive_constants);\n const primitive_constant_values =\n map(f => pair(head(tail(f)), false),\n primitive_constants);\n return pair(pair(\n append(primitive_function_names,\n primitive_constant_names),\n append(primitive_function_values,\n primitive_constant_values)),\n null);\n}\n\nfunction flatten_list_to_vectors(the_heads, the_tails, lst,\n make_ptr_fn, starting_index) {\n let free = starting_index;\n function helper(lst) {\n if (!is_pair(lst)) {\n return wrap_ptr(lst);\n } else {\n const index = free;\n free = free + 1;\n const elem = head(lst);\n the_heads[index] = helper(elem);\n the_tails[index] = helper(tail(lst));\n return make_ptr_fn(index);\n }\n }\n helper(lst);\n return free;\n}\n\n// MACHINE\nfunction get_contents(register) {\n return register(\"get\");\n}\n\nfunction set_contents(register, value) {\n return register(\"set\")(value);\n}\n\nfunction make_stack() {\n let stack = null;\n\n function push(x) {\n stack = pair(x, stack);\n return \"done\";\n }\n\n function pop() {\n if (is_null(stack)) {\n error(\"empty stack -- pop\");\n\n } else {\n const top = head(stack);\n stack = tail(stack);\n return top;\n }\n }\n\n function initialize() {\n stack = null;\n return \"done\";\n }\n\n function dispatch(message) {\n return message === \"push\"\n ? push\n : message === \"pop\"\n ? pop()\n : message === \"initialize\"\n ? initialize()\n : message === \"stack\"\n ? stack\n : error(message, \"unknown request -- stack\");\n }\n\n return dispatch;\n}\n\nfunction make_new_machine() {\n const SIZE = make_register(\"SIZE\");\n const pc = make_register(\"pc\");\n const flag = make_register(\"flag\");\n const stack = make_stack();\n const stack_reassign_proc = make_register(\"stack_reassign_proc\");\n const free = make_register(\"free\");\n const root = make_register(\"root\");\n const root_populate_proc = make_register(\"root_populate_proc\");\n const root_restore_proc = make_register(\"root_restore_proc\");\n const gc_registers = list(\n list(\"free\", free),\n list(\"scan\", make_register(\"scan\")),\n list(\"old\", make_register(\"old\")),\n list(\"new\", make_register(\"new\")),\n list(\"relocate_continue\", make_register(\"relocate_continue\")),\n list(\"temp\", make_register(\"temp\")),\n list(\"oldht\", make_register(\"oldht\"))\n );\n const exp = make_register(\"exp\");\n const env = make_register(\"env\");\n const evaluator_registers = list(\n list(\"exp\", exp),\n list(\"env\", env),\n list(\"val\", make_register(\"val\")),\n list(\"continue\", make_register(\"continue\")),\n list(\"fun\", make_register(\"fun\")),\n list(\"argl\", make_register(\"argl\")),\n list(\"unev\", make_register(\"unev\")),\n list(\"fun\", make_register(\"fun\"))\n );\n const aux_registers = list(\n list(\"res\", make_register(\"val\")),\n list(\"err\", make_register(\"err\")),\n list(\"a\", make_register(\"a\")),\n list(\"b\", make_register(\"b\")),\n list(\"c\", make_register(\"c\")),\n list(\"d\", make_register(\"d\")),\n list(\"e\", make_register(\"e\")),\n list(\"f\", make_register(\"f\"))\n );\n const the_heads = make_register(\"the_heads\");\n const the_tails = make_register(\"the_tails\");\n set_contents(the_heads, make_vector());\n set_contents(the_tails, make_vector());\n const new_heads = make_register(\"new_heads\");\n const new_tails = make_register(\"new_tails\");\n set_contents(new_heads, make_vector());\n set_contents(new_tails, make_vector());\n const prog_heads = make_register(\"prog_heads\");\n const prog_tails = make_register(\"prog_tails\");\n let the_instruction_sequence = null;\n let the_ops = list(list(\"initialize_stack\",\n () => stack(\"initialize\")));\n the_ops = append(the_ops, vector_ops);\n let register_table =\n list(list(\"SIZE\", SIZE),\n list(\"pc\", pc),\n list(\"flag\", flag),\n list(\"root\", root),\n list(\"root_populate_proc\", root_populate_proc),\n list(\"root_restore_proc\", root_restore_proc),\n list(\"stack_reassign_proc\", stack_reassign_proc),\n list(\"the_heads\", the_heads),\n list(\"the_tails\", the_tails),\n list(\"new_heads\", new_heads),\n list(\"new_tails\", new_tails),\n list(\"prog_heads\", prog_heads),\n list(\"prog_tails\", prog_tails));\n register_table = append(register_table, gc_registers);\n register_table = append(register_table, evaluator_registers);\n register_table = append(register_table, aux_registers);\n\n function start() {\n const root_registers =\n append(aux_registers, evaluator_registers);\n set_contents(pc, the_instruction_sequence);\n set_contents(free,\n make_ptr_ptr(flatten_list_to_vectors(\n the_heads(\"get\"),\n the_tails(\"get\"),\n setup_environment(),\n make_ptr_ptr,\n length(root_registers))));\n set_contents(env, make_ptr_ptr(length(root_registers)));\n function root_populate_proc_fn() {\n const root_ptr = free(\"get\");\n root(\"set\")(root_ptr);\n let register_list = root_registers;\n while (!is_null(register_list)) {\n const content = head(tail(head(register_list)))(\"get\");\n const index = unwrap_ptr(free(\"get\"));\n the_heads(\"get\")[index] =\n content === \"*unassigned*\"\n ? make_null_ptr() : content;\n free(\"set\")(make_ptr_ptr(index + 1));\n the_tails(\"get\")[index] = free(\"get\");\n register_list = tail(register_list);\n }\n the_tails(\"get\")[unwrap_ptr(free(\"get\")) - 1] =\n make_null_ptr();\n }\n function root_restore_proc_fn() {\n let root_ptr = root(\"get\");\n let register_list = root_registers;\n while (!is_null(register_list)) {\n const index = unwrap_ptr(root_ptr);\n const value = the_heads(\"get\")[index];\n head(tail(head(register_list)))(\"set\")(value);\n root_ptr = the_tails(\"get\")[index];\n register_list = tail(register_list);\n }\n }\n function stack_reassign_proc_fn() {\n let local_stack = stack(\"stack\");\n while (!is_null(local_stack)) {\n const value = head(local_stack);\n if (is_ptr_ptr(value)) {\n const index = unwrap_ptr(value);\n const new_ptr = the_tails(\"get\")[index];\n set_head(local_stack, new_ptr);\n } else {}\n local_stack = tail(local_stack);\n }\n }\n set_contents(root_populate_proc, root_populate_proc_fn);\n set_contents(root_restore_proc, root_restore_proc_fn);\n set_contents(stack_reassign_proc, stack_reassign_proc_fn);\n return execute();\n }\n function allocate_register(name) {\n if (is_undefined(assoc(name, register_table))) {\n register_table = pair(list(name, make_register(name)),\n register_table);\n } else {\n error(name, \"multiply defined register\");\n }\n return \"register allocated\";\n }\n function lookup_register(name) {\n const val = assoc(name, register_table);\n return is_undefined(val)\n ? error(name, \"unknown register\")\n : head(tail(val));\n }\n function execute() {\n const insts = get_contents(pc);\n if (is_null(insts)) {\n return \"done\";\n } else {\n const proc = inst_execution_fun(head(insts));\n proc();\n return execute();\n }\n }\n function dispatch(message) {\n return message === \"start\"\n ? start\n : message === \"install_instruction_sequence\"\n ? seq => { the_instruction_sequence = seq; }\n : message === \"allocate_register\"\n ? allocate_register\n : message === \"get_register\"\n ? lookup_register\n : message === \"install_operations\"\n ? ops => { the_ops = append(the_ops, ops); }\n : message === \"stack\"\n ? stack\n : message === \"operations\"\n ? the_ops\n : error(message, \"unknown request -- machine\");\n }\n return dispatch;\n}\n\nfunction make_machine(register_names, ops, controller) {\n const machine = make_new_machine();\n\n map(reg_name => machine(\"allocate_register\")(reg_name), register_names);\n machine(\"install_operations\")(ops);\n machine(\"install_instruction_sequence\")(assemble(controller, machine));\n\n return machine;\n}\n\nfunction start(machine) {\n return machine(\"start\")();\n}\n\nfunction get_register_contents(machine, register_name) {\n return get_contents(get_register(machine, register_name));\n}\n\nfunction set_register_contents(machine, register_name, value) {\n set_contents(get_register(machine, register_name), value);\n return \"done\";\n}\n\nfunction get_register(machine, reg_name) {\n return machine(\"get_register\")(reg_name);\n}\n\n// ASSEMBLER\n\nfunction assemble(controller, machine) {\n function receive(insts, labels) {\n update_insts(insts, labels, machine);\n return insts;\n }\n\n return extract_labels(controller, receive);\n}\n\nfunction extract_labels(text, receive) {\n function helper(insts, labels) {\n const next_inst = head(text);\n\n return is_string(next_inst)\n ? receive(insts, pair(make_label_entry(next_inst, insts), labels))\n : receive(pair(make_inst(next_inst), insts), labels);\n }\n\n return is_undefined(text) || is_null(text)\n ? receive(null, null)\n : extract_labels(tail(text), helper);\n}\n\nfunction update_insts(insts, labels, machine) {\n const pc = get_register(machine, \"pc\");\n const flag = get_register(machine, \"flag\");\n const stack = machine(\"stack\");\n const ops = machine(\"operations\");\n\n const set_iep = set_inst_execution_fun;\n const make_ep = make_execution_function;\n return map(i => set_iep(i,\n make_ep(inst_controller_instruction(i),\n labels,\n machine,\n pc,\n flag,\n stack,\n ops)),\n insts);\n}\n\nfunction make_inst(inst_controller_instruction) {\n return pair(inst_controller_instruction, null);\n}\n\nfunction inst_controller_instruction(inst) {\n return head(inst);\n}\n\nfunction inst_execution_fun(inst) {\n return tail(inst);\n}\n\nfunction set_inst_execution_fun(inst, fun) {\n set_tail(inst, fun);\n}\n\nfunction make_label_entry(label_name, insts) {\n return pair(label_name, insts);\n}\n\nfunction lookup_label(labels, label_name) {\n const val = assoc(label_name, labels);\n\n return is_undefined(val)\n ? error(label_name, \"undefined label -- assemble\")\n : tail(val);\n}\n\nfunction make_execution_function(inst, labels, machine, pc, flag, stack, ops) {\n const x = head(inst);\n\n return x === \"assign\"\n ? make_assign_ef(inst, machine, labels, ops, pc)\n : x === \"test\"\n ? make_test_ef(inst, machine, labels, ops, flag, pc)\n : x === \"branch\"\n ? make_branch_ef(inst, machine, labels, flag, pc)\n : x === \"go_to\"\n ? make_go_to_ef(inst, machine, labels, pc)\n : x === \"save\"\n ? make_save_ef(inst, machine, stack, pc)\n : x === \"restore\"\n ? make_restore_ef(inst, machine, stack, pc)\n : x === \"perform\"\n ? make_perform_ef(inst, machine, labels, ops, pc)\n : x === \"dump_memory\" // Added to allow printing the memory vectors\n ? () => {\n display(stringify(get_register_contents(machine, \"the_heads\")));\n display(stringify(get_register_contents(machine, \"the_tails\")));\n advance_pc(pc);\n }\n : error(inst, \"unknown instruction type -- assemble\");\n}\n\nfunction make_assign_ef(inst, machine, labels, operations, pc) {\n const target = get_register(machine, assign_reg_name(inst));\n const value_exp = assign_value_exp(inst);\n const value_fun = is_operation_exp(value_exp)\n ? make_operation_exp_ef(value_exp, machine, labels, operations)\n : make_primitive_exp_ef(value_exp, machine, labels);\n\n function perform_assign() {\n set_contents(target, value_fun());\n advance_pc(pc);\n }\n\n return perform_assign;\n}\n\nfunction assign_reg_name(assign_instruction) {\n return head(tail(assign_instruction));\n}\n\nfunction assign_value_exp(assign_instruction) {\n return head(tail(tail(assign_instruction)));\n}\n\nfunction assign(reg_name, value_exp) {\n return list(\"assign\", reg_name, value_exp);\n}\n\nfunction dump_memory() {\n return list(\"dump_memory\", \"the_heads\", \"the_tails\");\n}\n\nfunction advance_pc(pc) {\n set_contents(pc, tail(get_contents(pc)));\n\n}\n\nfunction make_test_ef(inst, machine, labels, operations, flag, pc) {\n const condition = test_condition(inst);\n\n if (is_operation_exp(condition)) {\n const condition_fun = make_operation_exp_ef(condition, machine,\n labels, operations);\n\n function perform_test() {\n set_contents(flag, unwrap_ptr(condition_fun()));\n advance_pc(pc);\n }\n\n return perform_test;\n } else {\n error(inst, \"bad test instruction -- assemble\");\n }\n}\n\nfunction test_condition(test_instruction) {\n return head(tail(test_instruction));\n}\n\nfunction test(condition) {\n return list(\"test\", condition);\n}\n\nfunction make_branch_ef(inst, machine, labels, flag, pc) {\n const dest = branch_dest(inst);\n\n if (is_label_exp(dest)) {\n const insts = lookup_label(labels, label_exp_label(dest));\n\n function perform_branch() {\n if (get_contents(flag)) {\n set_contents(pc, insts);\n\n } else {\n advance_pc(pc);\n }\n }\n\n return perform_branch;\n\n } else {\n error(inst, \"bad branch instruction -- assemble\");\n }\n}\n\nfunction branch_dest(branch_instruction) {\n return head(tail(branch_instruction));\n}\n\nfunction branch(dest) {\n return list(\"branch\", dest);\n}\n\nfunction make_goto(inst, machine, labels, pc) {\n const dest = goto_dest(inst);\n\n if (is_label_exp(dest)) {\n const insts = lookup_label(labels, label_exp_label(dest));\n return () => set_contents(pc, insts);\n\n } else if (is_register_exp(dest)) {\n const reg = get_register(machine, register_exp_reg(dest));\n return () => set_contents(pc, get_contents(reg));\n\n } else {\n error(inst, \"bad go_to instruction -- assemble\");\n }\n}\n\nfunction goto_dest(goto_instruction) {\n return head(tail(goto_instruction));\n}\n/*\nfunction go_to(dest) {\n return list(\"go_to\", dest);\n}\n*/\nfunction make_save_ef(inst, machine, stack, pc) {\n const reg = get_register(machine, stack_inst_reg_name(inst));\n\n function perform_save() {\n push(stack, get_contents(reg));\n advance_pc(pc);\n }\n\n return perform_save;\n}\n\nfunction make_restore_ef(inst, machine, stack, pc) {\n const reg = get_register(machine, stack_inst_reg_name(inst));\n\n function perform_restore() {\n set_contents(reg, pop(stack));\n advance_pc(pc);\n }\n\n return perform_restore;\n}\n\nfunction stack_inst_reg_name(stack_instruction) {\n return head(tail(stack_instruction));\n}\n\nfunction save(register_name) {\n return list(\"save\", register_name);\n}\n\nfunction restore(register_name) {\n return list(\"restore\", register_name);\n}\n\nfunction make_perform_ef(inst, machine, labels, operations, pc) {\n const action = perform_action(inst);\n\n if (is_operation_exp(action)) {\n const action_fun = make_operation_exp_ef(action, machine,\n labels, operations);\n return () => { action_fun(); advance_pc(pc); };\n\n } else {\n error(inst, \"bad perform instruction -- assemble\");\n }\n}\n\nfunction perform_action(inst) {\n return head(tail(inst));\n}\n\nfunction perform(op) {\n return list(\"perform\", op);\n}\n\nfunction make_primitive_exp_ef(exp, machine, labels) {\n if (is_constant_exp(exp)) {\n const c = constant_exp_value(exp);\n return () => c;\n\n } else if (is_label_exp(exp)) {\n const insts = lookup_label(labels, label_exp_label(exp));\n return () => insts;\n\n } else if (is_register_exp(exp)) {\n const r = get_register(machine, register_exp_reg(exp));\n return () => get_contents(r);\n\n } else {\n error(exp, \"unknown expression type -- assemble\");\n }\n}\n\n/* TODO: probably remove these -- suddenly available through new import chains\nfunction is_register_exp(exp) {\n return is_tagged_list(exp, \"reg\");\n}\n\nfunction register_exp_reg(exp) {\n return head(tail(exp));\n}\n\nfunction reg(name) {\n return list(\"reg\", name);\n}\n\nfunction is_constant_exp(exp) {\n return is_tagged_list(exp, \"constant\");\n}\n\nfunction constant_exp_value(exp) {\n return head(tail(exp));\n}\n\nfunction constant(value) {\n return list(\"constant\", wrap_ptr(value));\n}\n\nfunction is_label_exp(exp) {\n return is_tagged_list(exp, \"label\");\n}\n\nfunction label_exp_label(exp) {\n return head(tail(exp));\n}\n\nfunction label(string) {\n return list(\"label\", string);\n}\n*/\n\nfunction make_operation_exp_ef(exp, machine, labels, operations) {\n const op = lookup_prim(op_exp_op(exp), operations);\n const aprocs = map(e => make_primitive_exp_ef(e, machine, labels),\n operation_exp_operands(exp));\n\n function perform_operation_exp() {\n return op(map(p => p(), aprocs));\n }\n\n return perform_operation_exp;\n}\n\n/* TODO: probably remove these -- suddenly available through new import chains\nfunction is_operation_exp(exp) {\n return is_tagged_list(head(exp), \"op\");\n}\n\nfunction operation_exp_operands(operation_exp) {\n return tail(operation_exp);\n}\n\nfunction op(name) {\n return list(\"op\", name);\n}\n*/\n\nfunction op_exp_op(operation_exp) {\n return head(tail(head(operation_exp)));\n}\n\nfunction lookup_prim(symbol, operations) {\n const val = assoc(symbol, operations);\n\n return is_undefined(val)\n ? error(symbol, \"unknown operation -- assemble\")\n : head(tail(val));\n}\n\n// PAIR OPERATIONS\n\n// head in \"a\", tail in \"b\"\nconst pair_controller = list(\n \"pair\",\n save(\"continue\"),\n assign(\"continue\", label(\"pair_after_gc\")),\n test(list(op(\"===\"), reg(\"free\"), reg(\"SIZE\"))),\n branch(label(\"begin_garbage_collection\")),\n \"pair_after_gc\",\n restore(\"continue\"),\n perform(list(op(\"vector_set\"), reg(\"the_heads\"), reg(\"free\"), reg(\"a\"))),\n perform(list(op(\"vector_set\"), reg(\"the_tails\"), reg(\"free\"), reg(\"b\"))),\n assign(\"res\", reg(\"free\")),\n assign(\"free\", list(op(\"inc_ptr\"), reg(\"free\"))),\n go_to(reg(\"continue\"))\n);\n\nfunction underlying_javascript_closure(fn) {\n return args => apply_in_underlying_javascript(fn, args);\n}\n\nfunction unwrap_args(fn) {\n return args => fn(map(unwrap_ptr, args));\n}\n\nfunction wrap_return_value(fn) {\n return args => wrap_ptr(fn(args));\n}\n\nfunction primitive_function(fn) {\n return wrap_return_value(unwrap_args(underlying_javascript_closure(fn)));\n}\n\n// 5.3 MEMORY MANAGEMENT\n\nfunction vector_ref(vector, idx) {\n return vector[unwrap_ptr(idx)];\n}\n\nfunction vector_set(vector, idx, val) {\n vector[unwrap_ptr(idx)] = val;\n}\n\nfunction make_vector() {\n return [];\n}\n\nfunction inc_ptr(ptr) {\n return make_ptr_ptr(unwrap_ptr(ptr) + 1);\n}\n\nconst vector_ops = list(\n list(\"vector_ref\", underlying_javascript_closure(vector_ref)),\n list(\"vector_set\", underlying_javascript_closure(vector_set)),\n list(\"inc_ptr\", underlying_javascript_closure(inc_ptr))\n);\n\n// MACHINE SETUP\nconst ptr_ops =\n list(\n list(\"make_ptr_ptr\",\n underlying_javascript_closure(make_ptr_ptr)),\n list(\"make_null_ptr\",\n underlying_javascript_closure(make_null_ptr)),\n list(\"make_no_value_yet_ptr\",\n underlying_javascript_closure(make_no_value_yet_ptr)),\n list(\"make_prog_ptr\", underlying_javascript_closure(make_prog_ptr)),\n list(\"make_broken_heart_ptr\",\n underlying_javascript_closure(make_broken_heart_ptr)),\n list(\"is_number_ptr\",\n wrap_return_value(underlying_javascript_closure(is_number_ptr))),\n list(\"is_bool_ptr\",\n wrap_return_value(underlying_javascript_closure(is_bool_ptr))),\n list(\"is_string_ptr\",\n wrap_return_value(underlying_javascript_closure(is_string_ptr))),\n list(\"is_ptr_ptr\",\n wrap_return_value(underlying_javascript_closure(is_ptr_ptr))),\n list(\"is_null_ptr\",\n wrap_return_value(underlying_javascript_closure(is_null_ptr))),\n list(\"is_undefined_ptr\",\n wrap_return_value(underlying_javascript_closure(is_undefined_ptr))),\n list(\"is_prog_ptr\",\n wrap_return_value(underlying_javascript_closure(is_prog_ptr))),\n list(\"is_no_value_yet_ptr\",\n wrap_return_value(underlying_javascript_closure(is_no_value_yet_ptr))),\n list(\"is_broken_heart_ptr\",\n wrap_return_value(underlying_javascript_closure(is_broken_heart_ptr)))\n);\n\nconst primitive_ops = list(\n list(\"display\", primitive_function(display)),\n list(\"error\", primitive_function(error)),\n list(\"+\", primitive_function((x, y) => x + y)),\n list(\"-\", primitive_function((x, y) => x - y)),\n list(\"*\", primitive_function((x, y) => x * y)),\n list(\"/\", primitive_function((x, y) => x / y)),\n list(\"%\", primitive_function((x, y) => x % y)),\n list(\"===\", primitive_function((x, y) => x === y)),\n list(\"!==\", primitive_function((x, y) => x !== y)),\n list(\"<\", primitive_function((x, y) => x < y)),\n list(\"<=\", primitive_function((x, y) => x <= y)),\n list(\">\", primitive_function((x, y) => x > y)),\n list(\">=\", primitive_function((x, y) => x >= y)),\n list(\"!\", primitive_function(x => !x)),\n list(\"||\", primitive_function((x, y) => x || y)),\n list(\"&&\", primitive_function((x, y) => x && y))\n);\n\nconst gc_ops = list(\n list(\"call_proc\", underlying_javascript_closure(proc => proc()))\n);\n\nconst gc_controller = list(\n \"begin_garbage_collection\",\n perform(list(op(\"call_proc\"), reg(\"root_populate_proc\"))),\n assign(\"free\", list(op(\"make_ptr_ptr\"), constant(0))),\n assign(\"scan\", list(op(\"make_ptr_ptr\"), constant(0))),\n assign(\"old\", reg(\"root\")),\n assign(\"relocate_continue\", label(\"reassign_root\")),\n go_to(label(\"relocate_old_result_in_new\")),\n \"reassign_root\",\n assign(\"root\", reg(\"new\")),\n go_to(label(\"gc_loop\")),\n \"gc_loop\",\n test(list(op(\"===\"), reg(\"scan\"), reg(\"free\"))),\n branch(label(\"gc_flip\")),\n assign(\"old\", list(op(\"vector_ref\"), reg(\"new_heads\"), reg(\"scan\"))),\n assign(\"relocate_continue\", label(\"update_head\")),\n go_to(label(\"relocate_old_result_in_new\")),\n \"update_head\",\n perform(list(op(\"vector_set\"), reg(\"new_heads\"), reg(\"scan\"), reg(\"new\"))),\n assign(\"old\", list(op(\"vector_ref\"), reg(\"new_tails\"), reg(\"scan\"))),\n assign(\"relocate_continue\", label(\"update_tail\")),\n go_to(label(\"relocate_old_result_in_new\")),\n \"update_tail\",\n perform(list(op(\"vector_set\"), reg(\"new_tails\"), reg(\"scan\"), reg(\"new\"))),\n assign(\"scan\", list(op(\"inc_ptr\"), reg(\"scan\"))),\n go_to(label(\"gc_loop\")),\n \"relocate_old_result_in_new\",\n test(list(op(\"is_ptr_ptr\"), reg(\"old\"))),\n branch(label(\"gc_pair\")),\n assign(\"new\", reg(\"old\")),\n go_to(reg(\"relocate_continue\")),\n \"gc_pair\",\n assign(\"oldht\", list(op(\"vector_ref\"), reg(\"the_heads\"), reg(\"old\"))),\n test(list(op(\"is_broken_heart_ptr\"), reg(\"oldht\"))),\n branch(label(\"already_moved\")),\n assign(\"new\", reg(\"free\")),\n // new location for pair\n // Update free pointer\n assign(\"free\", list(op(\"inc_ptr\"), reg(\"free\"))),\n // Copy the head and tail to new memory\n perform(list(op(\"vector_set\"),\n reg(\"new_heads\"), reg(\"new\"), reg(\"oldht\"))),\n assign(\"oldht\", list(op(\"vector_ref\"), reg(\"the_tails\"), reg(\"old\"))),\n perform(list(op(\"vector_set\"),\n reg(\"new_tails\"), reg(\"new\"), reg(\"oldht\"))),\n // Construct the broken heart\n assign(\"oldht\", list(op(\"make_broken_heart_ptr\"))),\n perform(list(op(\"vector_set\"),\n reg(\"the_heads\"), reg(\"old\"), reg(\"oldht\"))),\n perform(list(op(\"vector_set\"),\n reg(\"the_tails\"), reg(\"old\"), reg(\"new\"))),\n go_to(reg(\"relocate_continue\")),\n \"already_moved\",\n assign(\"new\", list(op(\"vector_ref\"), reg(\"the_tails\"), reg(\"old\"))),\n go_to(reg(\"relocate_continue\")),\n \"gc_flip\",\n perform(list(op(\"call_proc\"), reg(\"stack_reassign_proc\"))),\n assign(\"temp\", reg(\"the_tails\")),\n assign(\"the_tails\", reg(\"new_tails\")),\n assign(\"new_tails\", reg(\"temp\")),\n assign(\"temp\", reg(\"the_heads\")),\n assign(\"the_heads\", reg(\"new_heads\")),\n assign(\"new_heads\", reg(\"temp\")),\n perform(list(op(\"call_proc\"), reg(\"root_restore_proc\"))),\n go_to(reg(\"continue\"))\n);\n\nconst error_controller = list(\n \"error\",\n perform(list(op(\"error\"), reg(\"res\"), reg(\"err\"))),\n go_to(label(\"end_evaluation\"))\n);\n\nconst begin_controller = list(\n \"fig_5_14\",\n \"pair4\",\n assign(\"a\", constant(4)),\n assign(\"b\", list(op(\"make_null_ptr\"))),\n assign(\"continue\", label(\"garbage1\")),\n go_to(label(\"pair\")),\n /// The following creates a garbage\n /// pair (9999, 9999) which will\n /// not affect live object count at\n /// the end of the program. You can\n /// verify by adding more garbage\n /// or remove this line. Or\n /// uncomment the use of\n /// dump_memory() below, before and\n /// after GC.\n \"garbage1\",\n assign(\"a\", constant(9999)),\n assign(\"b\", constant(9999)),\n assign(\"continue\", label(\"pair2\")),\n go_to(label(\"pair\")),\n \"pair2\",\n assign(\"a\", constant(3)),\n assign(\"b\", reg(\"res\")),\n assign(\"continue\", label(\"garbage2\")),\n go_to(label(\"pair\")),\n /// The following creates a garbage\n /// pair (9999, 9999) which will\n /// not affect live object count at\n /// the end of the program. You can\n /// verify by adding more garbage\n /// or remove this line. Or\n /// uncomment the use of\n /// dump_memory() below, before and\n /// after GC.\n \"garbage2\",\n assign(\"a\", constant(9999)),\n assign(\"b\", constant(9999)),\n assign(\"continue\", label(\"pair7\")),\n go_to(label(\"pair\")),\n \"pair7\",\n assign(\"temp\", reg(\"res\")),\n assign(\"a\", constant(2)),\n assign(\"b\", list(op(\"make_null_ptr\"))),\n assign(\"continue\", label(\"pair5\")),\n go_to(label(\"pair\")),\n \"pair5\",\n assign(\"a\", constant(1)),\n assign(\"b\", reg(\"res\")),\n assign(\"continue\", label(\"pair1\")),\n go_to(label(\"pair\")),\n \"pair1\",\n assign(\"a\", reg(\"res\")),\n assign(\"b\", reg(\"temp\")),\n assign(\"continue\", label(\"done\")),\n go_to(label(\"pair\")),\n \"done\",\n // dump_memory(), // uncomment to get a dump of heads and tails vectors\n assign(\"continue\", label(\"after_gc\")),\n go_to(label(\"begin_garbage_collection\")),\n \"after_gc\",\n // dump_memory(), // uncomment to get a dump of heads and tails vectors\n go_to(label(\"end_evaluation\")));\n\nconst end_controller = list(\n \"end_evaluation\"\n);\n\nconst ops = accumulate(append, null, list(\n vector_ops,\n ptr_ops,\n gc_ops,\n primitive_ops\n));\n\nconst controller = accumulate(append, null, list(\n begin_controller,\n pair_controller,\n gc_controller,\n error_controller,\n end_controller\n));\n\nfunction make_evaluator_machine(size) {\n const evaluator_machine = make_machine(null, ops, controller);\n set_register_contents(evaluator_machine, \"SIZE\", wrap_ptr(size));\n return evaluator_machine;\n}\n\nconst evaluator_machine = make_evaluator_machine(10000);\n\nset_register_contents(evaluator_machine, \"a\", wrap_ptr(206));\nset_register_contents(evaluator_machine, \"b\", wrap_ptr(40));\n\nstart(evaluator_machine);\nget_register_contents(evaluator_machine, \"free\");\n// [ 'ptr', 108 ] The number of live objects in the program at termination\n\n [ 'ptr', 108 ]\n\n\"gc_flip\",\n assign(\"temp\", reg(\"the_tails\")),\n assign(\"the_tails\", reg(\"new_tails\")),\n assign(\"new_tails\", reg(\"temp\")),\n assign(\"temp\", reg(\"the_heads\")),\n assign(\"the_heads\", reg(\"new_heads\")),\n assign(\"new_heads\", reg(\"temp\"))\n```", + "token_count": 2604, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Maintaining the Illusion of Infinite Memory", + "chunk_index": 6, + "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_6" + }, + { + "content": "A conventional computer memory can be thought of as an array of\ncubbyholes, each of which can contain a piece of information.\n\nEach\ncubbyhole has a unique name, called its\naddress or\nlocation.\n\nTypical memory systems provide two primitive operations:\none that fetches the data stored in a specified location and one that\nassigns new data to a specified location.\n\nMemory addresses can be\nincremented to support sequential access to some set of the\ncubbyholes.\n\nMore generally, many important data operations require\nthat memory addresses be treated as data, which can be stored in\nmemory locations and manipulated in machine registers.\n\nThe\nrepresentation of list structure is one application of such\naddress arithmetic.\n\nTo model computer memory, we use a new kind of data structure called a\nvector.\n\nAbstractly, a vector is a compound data object whose\nindividual elements can be accessed by means of an integer index in an\namount of time that is independent of the index.\nfunctions\nfor manipulating vectors:\n-\n-\nvector_ref(vector, n)\nn th element of the vector.\n-\n-\nvector_set(vector, n, value)\n$n$ th element of the vector to the\ndesignated value.\n\nFor example, if\nvector_ref(v, 5)\ngets the fifth entry in the vector\nvector_set(v, 5, 7)\nchanges the value of the fifth entry of the vector\nbase address\nthat specifies the beginning location of a vector in memory with an\nindex that specifies the offset of a particular element of the\nvector.\n\nWe can use vectors to implement the basic pair structures required for a\nlist-structured memory.\n\nLet us imagine that computer memory is divided into\ntwo vectors:\nthe_heads\nand\nthe_tails.\n\nWe will represent list structure as follows: A pointer to a pair is an index\ninto the two vectors.", + "token_count": 287, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Memory as Vectors", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Memory_as_Vectors_1" + }, + { + "content": "We will represent list structure as follows: A pointer to a pair is an index\ninto the two vectors.\n\nThe\nhead\nof the pair is the entry in\nthe_heads\nwith the designated index, and the\ntail\nof the pair is the entry in\nthe_tails\nwith the designated index.\n\nWe also need a representation for objects other\nthan pairs (such as numbers and\nstrings)\nand a way to distinguish one kind of data from another.\n\nThere are many\nmethods of accomplishing this, but they all reduce to using\ntyped pointers , that is, to extending the notion of\npointer to include information on data type. pair\ndata type and an index into the memory vectors) from pointers to other\nkinds of data (which consist of some other data type and whatever is\nbeing used to represent data of that type).\n\nTwo data objects are\n\n```javascript\n(===)\n```\n\nif their pointers are identical.\n\nFigure\nillustrates the use of this method to represent\nlist(list(1, 2), 3, 4),\nwhose box-and-pointer diagram is also shown.\n\nWe use letter prefixes to\ndenote the data-type information.\n\nThus, a pointer to the pair with\nindex 5 is denoted 4 is denoted\nhead\nand\ntail\nof the pair are stored.\n\nThe blank locations in\nthe_heads\nand\nthe_tails\nmay contain parts of other list structures (not of interest here).\n\nA pointer to a number, such as bignum data type, for which the pointer designates a list in which the parts of the number are\n\nstored.\n\nA string\ns printed\nrepresentation.\n\nThe parser constructs such a sequence\nwhen it encounters a string literal, and the\nstring-concatenation operator + and\nstring-producing\nprimitive functions such as\nstringify\nconstruct such a sequence.", + "token_count": 278, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Memory as Vectors", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Memory_as_Vectors_2" + }, + { + "content": "The parser constructs such a sequence\nwhen it encounters a string literal, and the\nstring-concatenation operator + and\nstring-producing\nprimitive functions such as\nstringify\nconstruct such a sequence.\n\nSince we want two instances of a string to\nbe recognized as the same string by\n=== and we want\n===\nto\nbe a simple test for equality of pointers, we must ensure that if the\nsystem sees the same string twice, it will use the same pointer (to\nthe same sequence of characters) to represent both occurrences.\n\nTo\naccomplish this, the system maintains a table, called the\nstring pool ,\nof all the strings it has ever encountered.\n\nWhen the system\nis about to construct a string, it checks the string pool to see if it has ever\nbefore seen the same string.\n\nIf it has not, it\nconstructs a new string (a typed pointer to a new\ncharacter sequence) and enters this pointer in the string pool.\n\nIf the\nsystem has seen the string before, it returns the string pointer\nstored in the string pool.\n\nThis process of replacing strings by unique\npointers is called\nstring interning.\n\nGiven the above representation , we can replace each\nprimitive list operation of a register machine with one or\nmore primitive vector operations.\n\nWe will use two registers,\nthe_heads\nand\nthe_tails,\nto identify the memory vectors, and will\nassume that\nvector_ref\nand\nvector_set\nare available as primitive operations.\n\nWe also assume that numeric\noperations on pointers (such as incrementing a pointer, using a pair pointer\nto index a vector, or adding two numbers) use only the index portion of\nthe typed pointer.\n\nFor example, we can make a register machine support the instructions\n\n```javascript\nassign(reg$_1$, list(op(\"head\"), reg(reg$_2$)))\n\nassign(reg$_1$, list(op(\"tail\"), reg(reg$_2$)))\n```\n\nif we implement these, respectively, as\n\n```javascript\nassign(reg$_1$, list(op(\"vector_ref\"), reg(\"the_heads\"), reg(reg$_2$)))\n\nassign(reg$_1$, list(op(\"vector_ref\"), reg(\"the_tails\"), reg(reg$_2$)))\n```", + "token_count": 304, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Memory as Vectors", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_Memory_as_Vectors_3" + }, + { + "content": "if we implement these, respectively, as\n\nThe instructions\n\n```javascript\nperform(list(op(\"set_head\"), reg(reg$_1$), reg(reg$_2$)))\n\nperform(list(op(\"set_tail\"), reg(reg$_1$), reg(reg$_2$)))\n```\n\nare implemented as\n\n```javascript\nperform(list(op(\"vector_set\"), reg(\"the_heads\"), reg(reg$_1$), reg(reg$_2$)))\n\nperform(list(op(\"vector_set\"), reg(\"the_tails\"), reg(reg$_1$), reg(reg$_2$)))\n```\n\n```javascript\nThe operation\n\tpair\n```\n\nis performed by allocating an unused index and storing the arguments to\npair\nin\nthe_heads\nand\nthe_tails\nat that indexed vector position.\n\nWe presume that there is a special\nregister,\n\n```javascript\nassign(reg$_1$, list(op(\"pair\"), reg(reg$_2$), reg(reg$_3$)))\n```\n\nis implemented as the following sequence of vector operations:\n\n```javascript\nperform(list(op(\"vector_set\"),\n reg(\"the_heads\"), reg(\"free\"), reg(reg$_2$))),\nperform(list(op(\"vector_set\"),\n reg(\"the_tails\"), reg(\"free\"), reg(reg$_3$))),\nassign(reg$_1$, reg(\"free\")),\nassign(\"free\", list(op(\"+\"), reg(\"free\"), constant(1)))\n```\n\nThe\n\n```javascript\n===\n```\n\noperation\n\n```javascript\nlist(op(\"===\"), reg(reg$_1$), reg(reg$_2$))\n```\n\nsimply tests the equality of all fields in the registers, and predicates such as is_pair, is_null, is_string, and is_number need only check the type field.\n\nAlthough our register machines use stacks, we need do nothing special\nhere, since stacks can be modeled in terms of lists.\n\nThe stack can be\na list of the saved values, pointed to by a special register\nthe_stack.\n\nThus,\nsave(reg)\ncan be implemented as\n\n```javascript\nassign(\"the_stack\", list(op(\"pair\"), reg(reg), reg(\"the_stack\")))\n```\n\nSimilarly, restore(reg) can be implemented as\n\n```javascript\nassign(reg, list(op(\"head\"), reg(\"the_stack\")))\nassign(\"the_stack\", list(op(\"tail\"), reg(\"the_stack\")))\n```\n\nand perform(list(op(\"initialize_stack\"))) can be implemented as\n\n```javascript\nassign(\"the_stack\", constant(null))\n```\n\nThese operations can be further expanded in terms of the vector\noperations given above.\n\nIn conventional computer architectures,\nhowever, it is usually advantageous to allocate the stack as a\nseparate vector.\n\nThen pushing and popping the stack can be\naccomplished by incrementing or decrementing an index into that\nvector.", + "token_count": 253, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Memory as Vectors", + "chunk_index": 4, + "chunk_id": "Computing_with_Register_Machines_Memory_as_Vectors_4" + }, + { + "content": "In section we saw how to\ntransform simple\nJavaScript\nprograms into descriptions of register\nmachines.\n\nWe will now perform this transformation on a more complex\nprogram, the metacircular evaluator of\nsections ,\nwhich shows how the behavior of a\nJavaScript\ninterpreter can be described in terms of the\nfunctions\nand explicit-control\nevaluator that we develop in this section shows how the underlying\nfunction-calling\nand argument-passing mechanisms used in the\nevaluation process can be described in terms of operations on\nregisters and stacks.\n\nIn addition, the explicit-control evaluator can\nserve as an implementation of a\nJavaScript\ninterpreter, written in a language that is very similar to the native machine\nlanguage of conventional computers.\n\nThe evaluator can be executed by the\nregister-machine simulator of section.\n\nAlternatively, it can be used as a starting point for building a\nmachine-language implementation of a\nJavaScript\nevaluator, or even a\nJavaScript programs.\n\nFigure shows such a hardware\nimplementation: a silicon chip that acts as an evaluator for\n, the language used in place of JavaScript in the original edition of this book.\n\nThe chip designers started with the data-path and controller specifications\nfor a register machine similar to the evaluator described in this section\nand used design automation programs to construct the\nintegrated-circuit layout.\n\nIn designing the explicit-control evaluator, we must specify the\noperations to be used in our register machine.\n\nWe described the\nmetacircular evaluator in terms of abstract syntax, using\nfunctions\nsuch as\nis_literal\nand\nmake_function.\n\nIn implementing the\nregister machine, we could expand these\nfunctions\ninto sequences of\nelementary list-structure memory operations, and implement these\noperations on our register machine.\n\nHowever, this would make our\nevaluator very long, obscuring the basic structure with\ndetails.", + "token_count": 281, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_The_Explicit-Control_Evaluator_1" + }, + { + "content": "However, this would make our\nevaluator very long, obscuring the basic structure with\ndetails.\n\nTo clarify the presentation, we will include as primitive\noperations of the register machine the syntax\nfunctions\ngiven in\nsection and the\nfunctions\nfor representing environments and other runtime data given in\nsections\nand.\n\nIn order to completely specify an evaluator that could be programmed\nin a low-level machine language or implemented in hardware, we would\nreplace these operations by more elementary operations, using the\nlist-structure implementation we described in\nsection.\n\nOur\nJavaScript\nevaluator register machine includes a stack and seven\nregisters:\nThe comp register\nis used to hold the\ncomponent\nto be evaluated, and component\nin the designated environment.\n\nThe.\n\n(The evaluator needs to call itself recursively, since\nevaluating\n\n```javascript\na\n component\n```\n\nrequires evaluating its subcomponents. ) The registers\n\nWe will not provide a data-path diagram to show how the registers and\noperations of the evaluator are connected, nor will we give the\ncomplete list of machine operations.\n\nThese are implicit in the\nevaluator s controller, which will be presented in detail.", + "token_count": 178, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_The_Explicit-Control_Evaluator_2" + }, + { + "content": "In our study of program design, we have seen that expert programmers\ncontrol the complexity of their designs with the same general\ntechniques used by designers of all complex systems.\n\nThey combine\nprimitive elements to form compound objects, they abstract compound\nobjects to form higher-level building blocks, and they preserve\nmodularity by adopting appropriate large-scale views of system\nstructure.\n\nIn illustrating these techniques, we have used\nJavaScript\nas a language for describing processes and for constructing computational\ndata objects and processes to model complex phenomena in the real world.\n\nHowever, as we confront increasingly complex problems, we will find that\nJavaScript,\nor indeed any fixed programming language, is not sufficient for our needs.\n\nWe must constantly turn to new languages in order to express our ideas more\neffectively.\n\nEstablishing new languages is a powerful strategy for\ncontrolling complexity in engineering design; we can often enhance our\nability to deal with a complex problem by adopting a new language that\nenables us to describe (and hence to think about) the problem in a different\nway, using primitives, means of combination, and means of abstraction that\nare particularly well suited to the problem at hand.\n\nProgramming is endowed with a multitude of languages.\n\nThere are\nphysical languages, such as the\nfunction declaration,\nthat are appropriate to the larger-scale organization of systems.\n\nMetalinguistic abstraction establishing\nplays an important role in all branches of engineering\ndesign.\n\nIt is particularly important to computer programming, because\nin programming not only can we formulate new languages but we can also\nimplement these languages by constructing evaluators.\n\nAn\nevaluator (or interpreter ) for a programming language is a\nfunction\nthat, when applied to\na statement or expression\nof the language, performs the actions required to evaluate that\nstatement or\nexpression.", + "token_count": 292, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": null, + "subsection": null, + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Metalinguistic_Abstraction_1" + }, + { + "content": "An\nevaluator (or interpreter ) for a programming language is a\nfunction\nthat, when applied to\na statement or expression\nof the language, performs the actions required to evaluate that\nstatement or\nexpression.\n\nIt is no exaggeration to regard this as the most fundamental idea in\nprogramming:\nThe evaluator, which determines the meaning of\nstatements and\nexpressions in a programming language, is just another program.\n\nTo appreciate this point is to change our images of ourselves as\nprogrammers.\n\nWe come to see ourselves as designers of languages,\nrather than only users of languages designed by others.\n\nIn fact, we can regard almost any program as the evaluator for some\nlanguage.\n\nFor instance, the polynomial manipulation system of\nsection embodies the rules of\npolynomial arithmetic and implements them in terms of operations on\nlist-structured data.\n\nIf we augment this system with\nfunctions\nto read and print polynomial expressions, we have the core of a\nspecial-purpose language for dealing with problems in symbolic mathematics.\n\nThe digital-logic simulator of\nsection and the constraint\npropagator of section are legitimate\nlanguages in their own right, each with its own primitives, means of\ncombination, and means of abstraction.\n\nSeen from this perspective, the\ntechnology for coping with large-scale computer systems merges with the\ntechnology for building new computer languages, and\n\nWe now embark on a tour of the technology by which languages are\nestablished in terms of other languages.\n\nIn this chapter we shall use\nJavaScript\nas a base, implementing evaluators as\nJavaScript\nfunctions.\n\nWe will take the first step in understanding how languages are implemented\nby building an evaluator for\nJavaScript\nitself.\n\nThe language implemented by our evaluator will be a subset of\nJavaScript.", + "token_count": 279, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": null, + "subsection": null, + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Metalinguistic_Abstraction_2" + }, + { + "content": "The language implemented by our evaluator will be a subset of\nJavaScript.\n\nAlthough the evaluator described in this chapter is written for a\nparticular\nsubset of JavaScript,\nit contains the essential structure of an evaluator for any\nlanguage\ndesigned for writing programs for a sequential machine.\n\n(In fact, most\nlanguage processors contain, deep within them, a little\nevaluator.)\nThe evaluator has been simplified for the purposes of illustration and\ndiscussion, and some features have been left out that would be\nimportant to include in a production-quality\nJavaScript\nsystem.\n\nNevertheless, this simple evaluator is adequate to execute most of\nthe programs in this book.\n\nAn important advantage of making the evaluator accessible as a\nJavaScript\nprogram is that we can implement alternative evaluation rules by describing\nthese as modifications to the evaluator program.\n\nOne place where we can use\nthis power to good effect is to gain extra control over the ways in which\ncomputational models embody the notion of time, which was so central to the\ndiscussion in chapter.\n\nThere, we mitigated some of the complexities\nof state and assignment by using streams to decouple the representation of\ntime in the world from time in the computer.\n\nOur stream programs, however,\nwere sometimes cumbersome, because they were constrained by the\napplicative-order evaluation of\nJavaScript.\n\nIn section , we ll change\nthe underlying language to provide for a more elegant approach, by modifying\nthe evaluator to provide for normal-order evaluation.\n\nSection implements a\nmore ambitious linguistic change, whereby statements and expressions\nhave many values, rather than just a single value.\n\nIn this language of\nnondeterministic computing , it is natural to express processes that\ngenerate all possible values for statements and expressions and then search\nfor those values that satisfy certain constraints.", + "token_count": 291, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": null, + "subsection": null, + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_Metalinguistic_Abstraction_3" + }, + { + "content": "In this language of\nnondeterministic computing , it is natural to express processes that\ngenerate all possible values for statements and expressions and then search\nfor those values that satisfy certain constraints.\n\nIn terms of models of\ncomputation and time, this is like having time branch into a set of\npossible futures and then searching for appropriate time\nlines.\n\nWith our nondeterministic evaluator, keeping track of multiple values\nand performing searches are handled automatically by the underlying\nmechanism of the language.\n\nIn section we implement a\nlogic-programming language in which knowledge is expressed in terms\nof relations, rather than in terms of computations with inputs and outputs.\n\nEven though this makes the language drastically different from\nJavaScript,\nor indeed from any conventional language, we will see that\nthe logic-programming evaluator shares the essential structure of the\nJavaScript\nevaluator.", + "token_count": 138, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": null, + "subsection": null, + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_Metalinguistic_Abstraction_4" + }, + { + "content": "Now that we have an evaluator expressed as a\nJavaScript\nprogram, we can experiment with alternative choices in\nJavaScript\nwith another member of the\nJavaScript\ncommunity, we can supply an evaluator that embodies\nthe change.\n\nThe recipient can then experiment with the new\nevaluator and send back comments as further modifications.\n\nNot only\ndoes the high-level implementation base make it easier to test and\ndebug the evaluator; in addition, the embedding enables the designer\nto snarf\nJavaScript\nevaluator uses primitives and control structure from the underlying\nJavaScript.\n\nOnly later (if ever) need the designer go to the trouble of building a\ncomplete implementation in a low-level language or in hardware.\n\nIn\nthis section and the next we explore some variations on\nJavaScript\nthat provide significant additional expressive power.", + "token_count": 128, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Lazy_Evaluation_1" + }, + { + "content": "In this section we will implement a normal-order language that is\nthe same as\nJavaScript\nexcept that compound\nfunctions\nare non-strict in each argument.\n\nPrimitive\nfunctions\nwill still be strict.\n\nIt is not difficult to modify the evaluator of\nsection so that the language it\ninterprets behaves this way.\n\nAlmost all the required changes center around\nfunction\napplication.\n\nThe basic idea is that, when applying a\nfunction,\nthe interpreter must determine which arguments are to be evaluated and which\nare to be delayed.\n\nThe delayed arguments are not evaluated; instead, they\nare transformed into objects called\nthunk s.\nfunction\napplication is being evaluated.\n\nThe process of evaluating the expression in a thunk is called\nforcing.\nfunction\nthat will use the value of the thunk; when it is the value of a predicate of\na conditional; and when it is the value of\na function expression\nthat is about to be\napplied as a\nfunction.\n\nOne design choice we have available is whether or not to\nmemoize thunks, similar to the optimization for streams in\nsection.\n\nWith memoization, the first\ntime a thunk is forced, it stores the value that is computed.\n\nSubsequent\nforcings simply return the stored value without repeating the computation.\n\nWe ll make our interpreter memoize, because this is more efficient for\nmany applications.\n\nThere are tricky considerations here,\nhowever.\n\nThe main difference between the lazy evaluator and the one in section is in the handling of function applications in evaluate and\n\n```javascript\nThe is_application\n\tclause of\n```\n\n```javascript\neval_lazy_example\n functions_4_1_1_lazy\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_program = parse(\"1; { true; 3; }\");\nevaluate(my_program, the_empty_environment);\n```", + "token_count": 267, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "An Interpreter with Lazy Evaluation", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_An_Interpreter_with_Lazy_Evaluation_1" + }, + { + "content": "The main difference between the lazy evaluator and the one in section is in the handling of function applications in evaluate and\n\n```javascript\neval_lazy\n eval_lazy_example\n 3\n\n: is_application(component)\n? apply(actual_value(function_expression(component), env),\n arg_expressions(component), env)\n\nfunction evaluate(component, env) {\n return is_literal(component)\n ? literal_value(component)\n : is_name(component)\n ? lookup_symbol_value(symbol_of_name(component), env)\n : is_application(component)\n ? apply(actual_value(function_expression(component), env),\n arg_expressions(component), env)\n : is_operator_combination(component)\n ? evaluate(operator_combination_to_application(component), env)\n : is_conditional(component)\n ? eval_conditional(component, env)\n : is_lambda_expression(component)\n ? make_function(lambda_parameter_symbols(component),\n lambda_body(component), env)\n : is_sequence(component)\n ? eval_sequence(sequence_statements(component), env)\n : is_block(component)\n ? eval_block(component, env)\n : is_return_statement(component)\n ? eval_return_statement(component, env)\n : is_function_declaration(component)\n ? evaluate(function_decl_to_constant_decl(component), env)\n : is_declaration(component)\n ? eval_declaration(component, env)\n : is_assignment(component)\n ? eval_assignment(component, env)\n : error(component, \"unknown syntax -- evaluate\");\n}\n```\n\nThis is almost the same as the\nis_application\nclause of\nevaluate\nin section.\n\nFor lazy evaluation,\nhowever, we call\nargument\nexpressions, rather than the arguments produced by evaluating them.\n\nSince\nwe will need the environment to construct thunks if the arguments are to be\ndelayed, we must pass this as well.\n\nWe still evaluate the\nfunction expression,\nbecause\nfunction\nto be applied in order to dispatch on its type (primitive versus compound)\nand apply it.\n\nWhenever we need the actual value of an expression, we use\n\n```javascript\nactual_value_lazy_example\n functions_4_1_1_lazy\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nactual_value(parse(\"1 + 2;\"), the_global_environment);\n```\n\n```javascript\nactual_value_lazy\n actual_value_lazy_example\n 3\n\nfunction actual_value(exp, env) {\n return force_it(evaluate(exp, env));\n}\n```\n\ninstead of just evaluate, so that if the expression s value is a thunk, it will be forced.\n\nOur new version of.\n\nThe difference is that\nevaluate\nhas passed in unevaluated\nargument\nexpressions: For primitive\nfunctions\n(which are strict), we evaluate all the arguments before applying the\nprimitive; for compound\nfunctions\n(which are non-strict) we delay all the\narguments before applying the\nfunction.\n\n```javascript\napply_lazy_example\n functions_4_1_1_lazy\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst plus = list(\"primitive\", (x, y) => x + y);\napply(plus, list(list(\"literal\", 1), list(\"literal\", 2)), the_global_environment);\n```", + "token_count": 303, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "An Interpreter with Lazy Evaluation", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_An_Interpreter_with_Lazy_Evaluation_2" + }, + { + "content": "The difference is that\nevaluate\nhas passed in unevaluated\nargument\nexpressions: For primitive\nfunctions\n(which are strict), we evaluate all the arguments before applying the\nprimitive; for compound\nfunctions\n(which are non-strict) we delay all the\narguments before applying the\nfunction.\n\n```javascript\napply_lazy\n list_of_arg_values\n apply_lazy_example\n 3\n\nfunction apply(fun, args, env) {\n if (is_primitive_function(fun)) {\n return apply_primitive_function(\n fun,\n list_of_arg_values(args, env)); // changed\n } else if (is_compound_function(fun)) {\n const result = evaluate(\n function_body(fun),\n extend_environment(\n function_parameters(fun),\n list_of_delayed_args(args, env), // changed\n function_environment(fun)));\n return is_return_value(result)\n ? return_value_content(result)\n : undefined;\n } else {\n error(fun, \"unknown function type -- apply\");\n }\n}\n```\n\nThe functions that process the arguments are just like list_of_values from section , except that list_of_delayed_args delays the arguments instead of evaluating them, and list_of_arg_values\n\nuses actual_value instead of evaluate:\n\n```javascript\nlist_of_arg_values\n\nfunction list_of_arg_values(exps, env) {\n return map(exp => actual_value(exp, env), exps);\n}\nfunction list_of_delayed_args(exps, env) {\n return map(exp => delay_it(exp, env), exps);\n}\n```\n\nThe other place we must change the evaluator is in the handling of conditionals, where we must use actual_value instead of to get the value\n\nof the predicate expression before testing whether it is true or false:\n\n```javascript\neval_if_lazy_example\n functions_4_1_1_lazy\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_cond_expr = parse(\"true ? 1 : 2;\");\neval_conditional(my_cond_expr, the_empty_environment);\n```\n\n```javascript\neval_if_lazy\n eval_if_lazy_example\n 1\n\nfunction eval_conditional(component, env) {\n return is_truthy(actual_value(conditional_predicate(component), env))\n ? evaluate(conditional_consequent(component), env)\n : evaluate(conditional_alternative(component), env);\n}\n```\n\nFinally, we must change the\ndriver_loop\nfunction\n(from section ) to use\nactual_@value\ninstead of\nso that if a delayed value is propagated back to the\nread-evaluate-print loop,\nit will be forced before being printed.\n\nWe also change the prompts to indicate that\nthis is the lazy evaluator:", + "token_count": 272, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "An Interpreter with Lazy Evaluation", + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_An_Interpreter_with_Lazy_Evaluation_3" + }, + { + "content": "We also change the prompts to indicate that\nthis is the lazy evaluator:\n\n```javascript\ndriver_loop_lazy\n functions_4_1_1_lazy\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n user_print\n user_read\n driver_loop_lazy_example\n\nconst input_prompt = \"L-evaluate input: \";\nconst output_prompt = \"L-evaluate value: \";\n\nfunction driver_loop(env) {\n const input = user_read(input_prompt);\n if (is_null(input)) {\n display(\"evaluator terminated\");\n } else {\n const program = parse(input);\n const locals = scan_out_declarations(program);\n const unassigneds = list_of_unassigned(locals);\n const program_env = extend_environment(locals, unassigneds, env);\n const output = actual_value(program, program_env);\n user_print(output_prompt, output);\n return driver_loop(program_env);\n }\n}\n\nconst input_prompt = \"L-evaluate input: \";\nconst output_prompt = \"L-evaluate value: \";\n\nfunction driver_loop(env) {\n const input = user_read(input_prompt);\n if (is_null(input)) {\n display(\"--- evaluator terminated ---\", \"\");\n } else {\n display(\"----------------------------\",\n input_prompt + \"\\n\" + input + \"\\n\");\n const program = parse(input);\n const locals = scan_out_declarations(program);\n const unassigneds = list_of_unassigned(locals);\n const program_env = extend_environment(locals, unassigneds, env);\n const output = actual_value(program, program_env);\n user_print(output_prompt, output);\n return driver_loop(program_env);\n }\n}\n```\n\nWith these changes made, we can start the evaluator and test it.\n\nThe\nsuccessful evaluation of the\ntry_me\nexpression\ndiscussed in section indicates\nthat the interpreter is performing lazy evaluation:\n\n```javascript\ndriver_loop_lazy_example\n driver_loop_lazy\n\nconst the_global_environment = setup_environment();\ndriver_loop(the_global_environment);\n\ndriver_loop(the_global_environment);\n\n// L-evaluate input:\n// function try_me(a, b) { return a === 0 ? 1 : b; }\n// L-evaluate value:\n// undefined\n\n// L-evaluate input:\n// try_me(0, head(null));\n// L-evaluate value:\n// 1\n```\n\n```javascript\nL-evaluate input:\n\nfunction try_me(a, b) {\n return a === 0 ? 1 : b;\n}\n```\n\n```javascript\nL-evaluate input:\n\n try_me(0, head(null));\n```\n\nOur evaluator must arrange to create thunks when\nfunctions\nare applied to arguments and to force these thunks later.\n\nA thunk must\npackage an expression together with the environment, so that the argument\ncan be produced later.\n\nTo force the thunk, we simply extract the expression\nand environment from the thunk and evaluate the expression in the\nenvironment.", + "token_count": 300, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "An Interpreter with Lazy Evaluation", + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_An_Interpreter_with_Lazy_Evaluation_4" + }, + { + "content": "To force the thunk, we simply extract the expression\nand environment from the thunk and evaluate the expression in the\nenvironment.\n\nWe use\nactual_value\nrather than\nso that in case the value of the expression is itself a thunk, we will force\nthat, and so on, until we reach something that is not a thunk:\n\n```javascript\nforce_it_lazy_v1\n\nfunction force_it(obj) {\n return is_thunk(obj)\n ? actual_value(thunk_exp(obj), thunk_env(obj))\n : obj;\n}\n```\n\nOne easy way to package an expression with an environment is to make a list\ncontaining the expression and the environment.\n\nThus, we create a thunk as\nfollows:\n\n```javascript\ndelay_it_lazy\n eval_lazy_example\n\nfunction delay_it(exp, env) {\n return list(\"thunk\", exp, env);\n}\nfunction is_thunk(obj) {\n return is_tagged_list(obj, \"thunk\");\n}\nfunction thunk_exp(thunk) { return head(tail(thunk)); }\n\nfunction thunk_env(thunk) { return head(tail(tail(thunk))); }\n```\n\nActually, what we want for our interpreter is not quite this, but\nrather thunks that have been memoized.\n\nWhen a thunk is forced, we will turn it into an evaluated thunk by replacing\nthe stored expression with its value and changing the\n\n```javascript\nforce_it_lazy\n eval_lazy_example\n\nfunction is_evaluated_thunk(obj) {\n return is_tagged_list(obj, \"evaluated_thunk\");\n}\nfunction thunk_value(evaluated_thunk) {\n return head(tail(evaluated_thunk));\n}\n\nfunction force_it(obj) {\n if (is_thunk(obj)) {\n const result = actual_value(thunk_exp(obj), thunk_env(obj));\n set_head(obj, \"evaluated_thunk\");\n set_head(tail(obj), result); // replace exp with its value\n set_tail(tail(obj), null); // forget unneeded env\n return result;\n } else if (is_evaluated_thunk(obj)) {\n return thunk_value(obj);\n } else {\n return obj;\n }\n}\n```\n\nNotice that the same delay_it function works both with and without memoization.", + "token_count": 243, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "An Interpreter with Lazy Evaluation", + "chunk_index": 5, + "chunk_id": "Metalinguistic_Abstraction_An_Interpreter_with_Lazy_Evaluation_5" + }, + { + "content": "In section , where we began\nour discussion of models of evaluation, we noted that\nJavaScript\nis an applicative-order language, namely, that all the arguments to\nJavaScript\nfunctions\nare evaluated when the\nfunction\nis applied.\n\nIn contrast, normal-order languages delay evaluation of\nfunction\narguments until the actual argument values are needed.\n\nDelaying evaluation of\nfunction\narguments until the last possible moment (e.g., until they are required by a\nprimitive operation) is called\nlazy evaluation.\nfunction\n\n```javascript\ntry_me_example\n\ntry_me(0, head(null));\n```\n\n```javascript\ntry_me\n try_me_example\n 1\n\nfunction try_me(a, b) {\n return a === 0 ? 1 : b;\n}\n\n// Source Academy opens this program\n// in lazy mode. Choose \"Source §2\" to\n// to compare with strict mode\nfunction try_me(a, b) {\n return a === 0 ? 1 : b;\n}\n```\n\nEvaluating\n\n```javascript\ntry_me(0, head(null));\n\tsignals\n```\n\nan error in\nJavaScript.\n\nWith lazy evaluation, there would be no error.\n\nEvaluating the\nstatement\nwould return 1, because the argument\nhead(null)\nwould never be evaluated.\n\nAn example that exploits lazy evaluation is the declaration of a function\n\n```javascript\nunless\n unless_example\n\nfunction unless(condition, usual_value, exceptional_value) {\n return condition ? exceptional_value : usual_value;\n}\n\n// Source Academy opens this program\n// in lazy mode. Choose \"Source §2\" to\n// to compare with strict mode\nfunction unless(condition, usual_value, exceptional_value) {\n return condition ? exceptional_value : usual_value;\n}\n```\n\nthat can be used in statements such as\n\n```javascript\nxs_is_null\n\nconst xs = null;\n```\n\n```javascript\nunless_example\n unless\n xs_is_null\n\nunless(is_null(xs), head(xs), display(\"error: xs should not be null\"));\n```\n\nThis won t work in an applicative-order language because both the\nusual value and the exceptional value will be evaluated before\n).\n\nAn advantage of lazy evaluation is\nthat some\nfunctions,\nsuch as\n\nIf the body of a\nfunction\nis entered before an argument has been evaluated we say that the\nfunction\nis\nnon-strict in that argument.", + "token_count": 308, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "Normal Order and Applicative Order", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Normal_Order_and_Applicative_Order_1" + }, + { + "content": "If the body of a\nfunction\nis entered before an argument has been evaluated we say that the\nfunction\nis\nnon-strict in that argument.\n\nIf the argument is evaluated before\nthe body of the\nfunction\nis entered we say that the\nfunction\nis\nstrict in that\nargument.\nfunctions\nare strict in each argument.\n\nIn a purely normal-order language, all compound\nfunctions\nare non-strict in each argument, and primitive\nfunctions\nmay be either strict or non-strict.\n\nThere are also languages (see\nexercise ) that give\nprogrammers detailed control over the strictness of the\nfunctions\nthey define.\n\nA striking example of a\nfunction\nthat can usefully be made non-strict is\npair\n(or, in general, almost any constructor for data structures).\n\nOne can do useful computation, combining elements to form\ndata structures and operating on the resulting data structures,\neven if the values of the elements are not known.\n\nIt makes perfect\nsense, for instance, to compute the length of a list without knowing\nthe values of the individual elements in the list.\n\nWe will exploit\nthis idea in section to implement the\nstreams of chapter as lists formed of non-strict\npairs.", + "token_count": 189, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "Normal Order and Applicative Order", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Normal_Order_and_Applicative_Order_2" + }, + { + "content": "In section , we showed how to\nimplement streams as delayed lists.\n\nWe used a\nto construct a\npromise to compute the\ntail\nof a stream, without actually fulfilling that promise until later.\n\n```javascript\nWe were forced to create streams as a new kind of data object similar\n\tbut not identical to lists, and this required us to reimplement many\n\tordinary list operations (\n```\n\nWith lazy evaluation, streams and lists can be identical, so there is\nno need for\nseparate list and stream operations.\n\nAll we need to do is to arrange matters\nso that\npair\nis non-strict.\n\nOne way to accomplish this is to extend the lazy evaluator\nto allow for non-strict primitives, and to implement\npair\nas one of these.\n\nAn easier way is to recall\n(section ) that there is no fundamental need\nto implement\npair\nas a primitive at all.\n\nInstead, we can represent\nfunctions :\n\n```javascript\npair_lazy_header\n\nconst my_pair_lazy = `\n```\n\n```javascript\npair_lazy_footer\n\nhead(tail(pair(1, pair(3, 2))));\n`;\n```\n\n```javascript\npair_lazy_example\n parse_and_evaluate_lazy\n pair_lazy_header\n pair_lazy\n pair_lazy_footer\n 3\n\nparse_and_evaluate(my_pair_lazy);\n```\n\n```javascript\npair_lazy\n\nfunction pair(x, y) {\n return m => m(x, y);\n}\nfunction head(z) {\n return z((p, q) => p);\n}\nfunction tail(z) {\n return z((p, q) => q);\n}\n```\n\nIn terms of these basic operations, the standard definitions of the list\noperations will work with infinite lists (streams) as well as finite ones,\nand the stream operations can be implemented as list operations.\n\nHere are\nsome examples:\n\n```javascript\nlist_lib_test_header\n\nconst my_list_lib_test = `\n```\n\n```javascript\nlist_lib_test_footer\n\nlist_ref(integers, 17);\n`;\n```\n\n```javascript\nlist_lib_test\n parse_and_evaluate_lazy\n list_lib_test_header\n pair_lazy\n list_library_lazy\n list_lib_test_header\n list_lib_test_footer\n 18\n\nparse_and_evaluate(my_list_lib_test);\n```", + "token_count": 267, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "Streams as Lazy Lists", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Streams_as_Lazy_Lists_1" + }, + { + "content": "Here are\nsome examples:\n\n```javascript\nlist_library_lazy\n\nfunction list_ref(items, n) {\n return n === 0\n ? head(items)\n : list_ref(tail(items), n - 1);\n}\nfunction map(fun, items) {\n return is_null(items)\n ? null\n : pair(fun(head(items)),\n map(fun, tail(items)));\n}\nfunction scale_list(items, factor) {\n return map(x => x * factor, items);\n}\nfunction add_lists(list1, list2) {\n return is_null(list1)\n ? list2\n : is_null(list2)\n ? list1\n : pair(head(list1) + head(list2),\n add_lists(tail(list1), tail(list2)));\n}\nconst ones = pair(1, ones);\nconst integers = pair(1, add_lists(ones, integers));\n```\n\n```javascript\nL-evaluate input:\n\nlist_ref(integers, 17);\n```\n\nNote that these lazy lists are even lazier than the streams of\nchapter : The\nhead\nof the list, as well as the\ntail,\nis delayed.\nhead\nor\ntail\nof a lazy pair need not force the value of a list element.\n\nThe value will be\nforced only when it is really needed e.g., for use as the argument\nof a primitive, or to be printed as an answer.\n\nLazy pairs also help with the problem that arose with streams in\nsection , where we\nfound that formulating stream models of systems with loops may require us to\nsprinkle our programs with\nadditional lambda expressions for\nWith lazy evaluation, all arguments to\nfunctions\nare delayed uniformly.\n\nFor instance, we can implement\nfunctions\nto integrate lists and solve differential equations as we originally\nintended in section :\n\n```javascript\nlazy_integral_header\n\nconst my_integral = `\n```\n\n```javascript\nlazy_integral_footer\n\nlist_ref(solve(x => x, 1, 0.1), 10);\n`;\n```\n\n```javascript\nlazy_integral_test\n parse_and_evaluate_lazy\n lazy_integral_header\n pair_lazy\n list_library_lazy\n lazy_integral\n lazy_integral_footer\n 2.5937424601\n\nparse_and_evaluate(my_integral);\n```\n\n```javascript\nlazy_integral\n\nfunction integral(integrand, initial_value, dt) {\n const int = pair(initial_value,\n add_lists(scale_list(integrand, dt),\n int));\n return int;\n}\nfunction solve(f, y0, dt) {\n const y = integral(dy, y0, dt);\n const dy = map(f, y);\n return y;\n}\n```\n\n```javascript\nL-evaluate input:\n\nlist_ref(solve(x => x, 1, 0.001), 1000);\n```", + "token_count": 295, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "Streams as Lazy Lists", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Streams_as_Lazy_Lists_2" + }, + { + "content": "In chapter we stressed that computer science deals with\n\nMost programming languages, including\nJavaScript,\nare organized around\ncomputing the values of mathematical functions.\n\nExpression-oriented\nlanguages\n(such as , C, Python, and JavaScript)\ncapitalize on the\npun that an expression that describes the value of a\nfunction may also be interpreted as a means of computing that value.\n\nBecause of this, most programming languages are strongly biased toward\nunidirectional computations (computations with well-defined inputs and\noutputs).\n\nThere are, however, radically different programming languages\nthat relax this bias.\n\nWe saw one such example in\nsection , where the objects of\ncomputation were arithmetic constraints.\n\nIn a constraint system the\ndirection and the order of computation are not so well specified; in\ncarrying out a computation the system must therefore provide more detailed\nhow to knowledge than would be the case with an ordinary\narithmetic computation.\n\nThis does not mean, however, that the user is\nreleased altogether from the responsibility of providing imperative\nknowledge.\n\nThere are many constraint networks that implement the same set\nof constraints, and the user must choose from the set of mathematically\nequivalent networks a suitable network to specify a particular computation.\n\nThe nondeterministic program evaluator of\nsection also moves\naway from the view that programming is about constructing algorithms for\ncomputing unidirectional functions.\n\nIn a nondeterministic language,\nexpressions can have more than one value, and, as a result, the\ncomputation is\ndealing with\nunification.\n\nThis approach, when it works, can be a very\nwhat is\nfact can be used to solve a number of different problems that would have\ndifferent how to components.\n\nAs an example, consider the\nJavaScript,\nwe could define\npair,\nas we did in section :\n\n```javascript\nfunction append(x, y) {\n return is_null(x)\n ? y\n : pair(head(x), append(tail(x), y));\n}\n```", + "token_count": 296, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Logic_Programming_1" + }, + { + "content": "As an example, consider the\nJavaScript,\nwe could define\npair,\nas we did in section :\n\nThis\nfunction\ncan be regarded as a translation into\nJavaScript\nof the following two rules, the first of which covers the case where the\nfirst list is empty and the second of which handles the case of a nonempty\nlist, which is a\npair\nof two parts:\n-\n-\nFor any list\n-\n-\nFor any\npair(u, v)\nand\npair(u, z)\nif\nUsing the\nfunction,\nwe can answer questions such as\nFind the\nlist(\"a\", \"b\")\nand\nlist(\"c\", \"d\").\n\nBut the same two rules are also sufficient for answering the following\nsorts of questions, which the\nfunction\ncan t answer:\nFind a list\nlist(\"a\", \"b\")\nto produce\nlist(\"a\", \"b\", \"c\", \"d\").\n\nFind all\nlist(\"a\", \"b\", \"c\", \"d\").\n\nIn a\nfunction\nby stating the two rules about How to knowledge is provided automatically by the\ninterpreter to allow this single pair of rules to be used to answer all\nthree types of questions about\n\nContemporary logic programming languages (including the one we\nimplement here) have substantial deficiencies, in that their general\nhow to methods can lead them into spurious infinite loops or\nother undesirable behavior.\n\nLogic programming is an active field of research\nin computer science.\n\nEarlier in this chapter we explored the technology of implementing\ninterpreters and described the elements that are essential to an\ninterpreter for a\nJavaScript-like\nlanguage (indeed, to an interpreter for any conventional language).\n\nNow we\nwill apply these ideas to discuss an interpreter for a logic programming\nlanguage.\n\nWe call this\nlanguage the\nquery language , because it is very useful for\nretrieving information from data bases by formulating\nqueries , or questions, expressed in the language.", + "token_count": 287, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Logic_Programming_2" + }, + { + "content": "We call this\nlanguage the\nquery language , because it is very useful for\nretrieving information from data bases by formulating\nqueries , or questions, expressed in the language.\n\nEven though the\nquery language is very different from\nJavaScript,\nwe will find it convenient to describe the language in terms of the same\ngeneral framework we have been using all along: as a collection of primitive\nelements, together with means of combination that enable us to combine\nsimple elements to create more complex elements and means of abstraction\nthat enable us to regard complex elements as single conceptual units.\n\nAn\ninterpreter for a logic programming language is considerably more complex\nthan an interpreter for a language like\nJavaScript.\n\nNevertheless, we will see\nthat our.\n\nIn\nparticular, there will be an evaluate part that classifies\nexpressions according to type and an apply part that\nimplements the language s abstraction mechanism\n(functions\nin the case of\nJavaScript,\nand rules in the case of logic programming).\n\nAlso, a central role\nis played in the implementation by a frame data structure, which determines\nthe correspondence between symbols and their associated values.\n\nOne\nadditional interesting aspect of our query-language implementation is\nthat we make substantial use of streams, which were introduced in\nchapter.", + "token_count": 208, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": null, + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_Logic_Programming_3" + }, + { + "content": "In this section, we extend the JavaScript evaluator to support a programming paradigm called nondeterministic computing by building into the evaluator a facility to support\n\n.\n\nNondeterministic computing, like stream processing, is useful for\ngenerate and test applications.\n\nConsider the task of\nstarting with two lists of positive integers and finding a pair of\nintegers one from the first list and one from the second\nlist whose sum is prime.\n\nWe saw how to handle this with finite\nsequence operations in section and\nwith infinite streams in section.\n\nOur approach was to generate the sequence of all possible pairs and filter\nthese to select the pairs whose sum is prime.\n\nWhether we actually generate\nthe entire sequence of pairs first as in chapter , or interleave the\ngenerating and filtering as in chapter , is immaterial to the\nessential image of how the computation is organized.\n\nThe nondeterministic approach evokes a different image.\n\nImagine simply\nthat we choose (in some way) a number from the first list and a number\nfrom the second list and require (using some mechanism) that their\nfunction:\n\n```javascript\nprime_sum_pair_non_det\n prime_sum_pair_non_det_example\n [ 3, [ 20, null ] ]\n\nfunction prime_sum_pair(list1, list2) {\n const a = an_element_of(list1);\n const b = an_element_of(list2);\n require(is_prime(a + b));\n return list(a, b);\n}\n```\n\nIt might seem as if this\nfunction\nmerely restates the problem,\nrather than specifying a way to solve it.\n\nNevertheless, this is a\nlegitimate nondeterministic program.\n\nThe key idea here is that\ncomponents\nin a nondeterministic language\ncan have more than one possible value.\n\nFor instance,\nan_element_of\nmight return any element of the given list.\n\nOur nondeterministic program\nevaluator will work by automatically choosing a possible value and keeping\ntrack of the choice.", + "token_count": 285, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Nondeterministic_Computing_1" + }, + { + "content": "Our nondeterministic program\nevaluator will work by automatically choosing a possible value and keeping\ntrack of the choice.\n\nIf a subsequent requirement is not met, the evaluator\nwill try a different choice, and it will keep trying new choices until the\nevaluation succeeds, or until we run out of choices.\n\nJust as the lazy\nevaluator freed the programmer from the details of how values are delayed\nand forced, the nondeterministic program evaluator will free the programmer\nfrom the details of how choices are made.\n\nIt is instructive to contrast the different images of\na component\nrepresents the exploration\nof a set of possible worlds, each determined by a set of choices.\n\nSome of the possible worlds lead to dead ends, while others have\nuseful values.\n\nThe nondeterministic program evaluator supports the\nillusion that time branches, and that our programs have different\npossible execution histories.\n\nWhen we reach a dead end, we can\nrevisit a previous choice point and proceed along a different branch.\n\nThe nondeterministic program evaluator implemented below is called the a new syntactic form called declaration of prime_sum_pair at the declarations of is_prime, an_element_of, and function\n\nas follows:\n\n```javascript\nprime_sum_pair_non_det_example\n is_prime2\n prime_sum_pair_non_det\n\namb-evaluate input:\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\nThe value returned was obtained after the evaluator repeatedly chose elements from each of the lists, until a successful choice was made.\n\nSection introduces\ns automatic search mechanism.\n\nSection presents examples of\nnondeterministic programs, and\nsection gives the details of how\nto implement the\nJavaScript\nevaluator.", + "token_count": 277, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Nondeterministic_Computing_2" + }, + { + "content": "Section describes the implementation of the\n\nThe following puzzle (adapted from The software company\n\nWe can determine who moves into which office in a straightforward way by enumerating all the possibilities and imposing the given restrictions:\n\n```javascript\noffice_move\n distinct\n office_move_example\n [ 'alyssa', [ 3, null ] ]\n\nfunction office_move() {\n const alyssa = amb(1, 2, 3, 4, 5);\n const ben = amb(1, 2, 3, 4, 5);\n const cy = amb(1, 2, 3, 4, 5);\n const lem = amb(1, 2, 3, 4, 5);\n const louis = amb(1, 2, 3, 4, 5);\n require(distinct(list(alyssa, ben, cy, lem, louis)));\n require(alyssa !== 5);\n require(ben !== 1);\n require(cy !== 5);\n require(cy !== 1);\n require(lem > ben);\n require(math_abs(louis - cy) !== 1);\n require(math_abs(cy - ben) !== 1);\n return list(list(\"alyssa\", alyssa),\n list(\"ben\", ben),\n list(\"cy\", cy),\n list(\"lem\", lem),\n list(\"louis\", louis));\n}\n```\n\n```javascript\noffice_move_example\n\noffice_move();\n\nhead(office_move());\n```\n\nEvaluating the expression office_move() produces the result\n\n```javascript\nlist(list(\"alyssa\", 3), list(\"ben\", 2), list(\"cy\", 4),\n list(\"lem\", 5), list(\"louis\", 1))\n```\n\nAlthough this simple\nfunction\nworks, it is very slow.\n\nExercises\nand discuss some possible\nimprovements.\n\nPrograms designed to accept natural language as input usually start by\nattempting to parse the input, that is, to match the input\nagainst some grammatical structure.\n\nFor example, we might try to\nrecognize simple sentences consisting of an article followed by a noun\nfollowed by a verb, such as The cat eats.\n\nTo accomplish\nsuch an analysis, we must be able to identify the parts of speech of\nindividual words.\n\nWe could start with some lists that classify various\nwords:\n\n```javascript\nnouns\n\nconst nouns = list(\"noun\", \"student\", \"professor\", \"cat\", \"class\");\n\nconst verbs = list(\"verb\", \"studies\", \"lectures\", \"eats\", \"sleeps\");\n\nconst articles = list(\"article\", \"the\", \"a\");\n```\n\nWe also need a\ngrammar , that is, a set of rules describing how\ngrammatical elements are composed from simpler elements.", + "token_count": 298, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Examples of Nondeterministic Programs", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Examples_of_Nondeterministic_Programs_1" + }, + { + "content": "We also need a\ngrammar , that is, a set of rules describing how\ngrammatical elements are composed from simpler elements.\n\nA very\nsimple grammar might stipulate that a sentence always consists of two\npieces a noun phrase followed by a verb and that a noun\nphrase consists of an article followed by a noun.\n\nWith this grammar, the\nsentence The cat eats is parsed as follows:\n\n```javascript\nlist(\"sentence\",\n list(\"noun-phrase\", list(\"article\", \"the\"), list(\"noun\", \"cat\"),\n list(\"verb\", \"eats\"))\n```\n\nWe can generate such a parse with a simple program that has separate\nfunctions\nfor each of the grammatical rules.\n\nTo parse a sentence, we identify its\ntwo constituent pieces and return a list of these two elements, tagged with\nthe symbol\n\n```javascript\nparse_sentence\n parse_noun_phrase\n nouns\n parse_input_example\n [ \"sentence\", [ [\"noun-phrase\", [[\"article\", [\"the\", null]], [[\"noun\", [\"cat\", null]], null]]], [[\"verb\", [\"eats\", null]], null]]]\n\nfunction parse_sentence() {\n return list(\"sentence\",\n parse_noun_phrase(),\n parse_word(verbs));\n}\n```\n\nA noun phrase, similarly, is parsed by finding an article followed by a noun:\n\n```javascript\nparse_noun_phrase\n nouns\n parse_word\n parse_input_example\n\nfunction parse_noun_phrase() {\n return list(\"noun-phrase\",\n parse_word(articles),\n parse_word(nouns));\n}\n```\n\nAt the lowest level, parsing boils down to repeatedly checking that\nthe next\nnot-yet-parsed\nword is a member of the list of words for the\nrequired part of speech.\n\nTo implement this, we maintain a global\nvariable\nnot_yet_parsed,\nwhich is the input that has not yet been parsed.\n\nEach time we check a word,\nwe require that\nnot_yet_parsed\nmust be nonempty and that it should begin with a word from the designated\nlist.\n\nIf so, we remove that word from\nnot_yet_parsed\nand return the word together with its part of speech (which is found at\nthe head of the list):\n\n```javascript\nparse_word\n unparsed\n parse_input_example\n\nfunction parse_word(word_list) {\n require(! is_null(not_yet_parsed));\n require(! is_null(member(head(not_yet_parsed), tail(word_list))));\n const found_word = head(not_yet_parsed);\n not_yet_parsed = tail(not_yet_parsed);\n return list(head(word_list), found_word);\n}\n```", + "token_count": 300, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Examples of Nondeterministic Programs", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Examples_of_Nondeterministic_Programs_2" + }, + { + "content": "If so, we remove that word from\nnot_yet_parsed\nand return the word together with its part of speech (which is found at\nthe head of the list):\n\nTo start the parsing, all we need to do is set not_yet_parsed to be the entire input, try to parse a sentence, and check that\n\nnothing is left over:\n\n```javascript\nunparsed\n\nlet not_yet_parsed = null;\n```\n\n```javascript\nparse_input\n unparsed\n parse_sentence\n parse_input_example\n\nfunction parse_input(input) {\n not_yet_parsed = input;\n const sent = parse_sentence();\n require(is_null(not_yet_parsed));\n return sent;\n}\n```\n\nWe can now try the parser and verify that it works for our simple test sentence:\n\n```javascript\nparse_input_example\n parse_input\n\namb-evaluate input:\n\nparse_input(list(\"the\", \"cat\", \"eats\"));\n\nparse_input(list(\"the\", \"cat\", \"eats\"));\n```\n\nThe\n\nLet s add to our grammar a list of prepositions:\n\n```javascript\nprepositions\n\nconst prepositions = list(\"prep\", \"for\", \"to\", \"in\", \"by\", \"with\");\n```\n\nand define a prepositional phrase (e.g., for the cat ) to be a preposition followed by a noun phrase:\n\n```javascript\nparse_prepositional_phrase\n parse_word\n prepositions\n parse_input_example_2\n [ \"sentence\", [ [ \"noun-phrase\", [ [ \"simple-noun-phrase\", [[\"article\", [\"the\", null]], [[\"noun\", [\"student\", null]], null]]], [ [ \"prep-phrase\", [ [\"prep\", [\"with\", null]], [ [ \"simple-noun-phrase\", [[\"article\", [\"the\", null]], [[\"noun\", [\"cat\", null]], null]]], null]]], null]]], [ [ \"verb-phrase\", [ [\"verb\", [\"sleeps\", null]], [ [ \"prep-phrase\", [ [\"prep\", [\"in\", null]], [ [ \"simple-noun-phrase\", [[\"article\", [\"the\", null]], [[\"noun\", [\"class\", null]], null]]], null]]], null]]], null]]]\n\nfunction parse_prepositional_phrase() {\n return list(\"prep-phrase\",\n parse_word(prepositions),\n parse_noun_phrase());\n}\n```\n\nNow we can define a sentence to be a noun phrase followed by a verb phrase, where a verb phrase can be either a verb\n\nor a verb phrase extended by a prepositional phrase:\n\n```javascript\nparse_sentence_2\n parse_prepositional_phrase\n parse_word\n parse_noun_phrase_2\n parse_input_example_2\n\nfunction parse_sentence() {\n return list(\"sentence\",\n parse_noun_phrase(),\n parse_verb_phrase());\n}\nfunction parse_verb_phrase() {\n function maybe_extend(verb_phrase) {\n return amb(verb_phrase,\n maybe_extend(list(\"verb-phrase\",\n verb_phrase,\n parse_prepositional_phrase())));\n }\n return maybe_extend(parse_word(verbs));\n}\n```\n\n```javascript\nparse_input_2\n unparsed\n parse_sentence_2\n\nfunction parse_input(input) {\n not_yet_parsed = input;\n const sent = parse_sentence();\n require(is_null(not_yet_parsed));\n return sent;\n}\n```", + "token_count": 311, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Examples of Nondeterministic Programs", + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_Examples_of_Nondeterministic_Programs_3" + }, + { + "content": "or a verb phrase extended by a prepositional phrase:\n\nWhile we re at it, we can also elaborate the definition of noun\nphrases to permit such things as a cat in the class.\n\nWhat\nwe used to call a noun phrase, we ll now call a simple noun phrase,\nand a noun phrase will now be either a simple noun phrase or a noun phrase\nextended by a prepositional phrase:\n\n```javascript\nparse_noun_phrase_2\n parse_prepositional_phrase\n parse_word\n nouns\n parse_input_example_2\n\nfunction parse_simple_noun_phrase() {\n return list(\"simple-noun-phrase\",\n parse_word(articles),\n parse_word(nouns));\n}\nfunction parse_noun_phrase() {\n function maybe_extend(noun_phrase) {\n return amb(noun_phrase,\n maybe_extend(list(\"noun-phrase\",\n noun_phrase,\n parse_prepositional_phrase())));\n }\n return maybe_extend(parse_simple_noun_phrase());\n}\n```\n\nOur new grammar lets us parse more complex sentences.\n\nFor example\n\n```javascript\nparse_input_example_2\n parse_input_2\n\nparse_input(list(\"the\", \"student\", \"with\", \"the\", \"cat\",\n \"sleeps\", \"in\", \"the\", \"class\"));\n```\n\nproduces\n\n```javascript\nlist(\"sentence\",\n list(\"noun-phrase\",\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"), list(\"noun\", \"student\")),\n list(\"prep-phrase\", list(\"prep\", \"with\"),\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n list(\"noun\", \"cat\")))),\n list(\"verb-phrase\",\n list(\"verb\", \"sleeps\"),\n list(\"prep-phrase\", list(\"prep\", \"in\"),\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n list(\"noun\", \"class\")))))\n```\n\nObserve that a given input may have more than one legal parse.\n\nIn the\nsentence The professor lectures to the student with the cat,\nit may be that the professor is lecturing with the cat, or that the student\nhas the cat.\n\nOur nondeterministic program finds both possibilities:\n\n```javascript\nmultiple_legal_parses\n parse_input_2\n\nparse_input(list(\"the\", \"professor\", \"lectures\",\n \"to\", \"the\", \"student\", \"with\", \"the\", \"cat\"));\n```\n\nproduces\n\n```javascript\nlist(\"sentence\",\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"), list(\"noun\", \"professor\")),\n list(\"verb-phrase\",\n list(\"verb-phrase\",\n list(\"verb\", \"lectures\"),\n list(\"prep-phrase\", list(\"prep\", \"to\"),\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n\t\t list(\"noun\", \"student\")))),\n list(\"prep-phrase\", list(\"prep\", \"with\"),\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n list(\"noun\", \"cat\")))))\n```\n\nAsking the evaluator to retry yields\n\n```javascript\nlist(\"sentence\",\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"), list(\"noun\", \"professor\")),\n list(\"verb-phrase\",\n list(\"verb\", \"lectures\"),\n list(\"prep-phrase\", list(\"prep\", \"to\"),\n list(\"noun-phrase\",\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n list(\"noun\", \"student\")),\n list(\"prep-phrase\", list(\"prep\", \"with\"),\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n list(\"noun\", \"cat\")))))))\n```", + "token_count": 277, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Examples of Nondeterministic Programs", + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_Examples_of_Nondeterministic_Programs_4" + }, + { + "content": "The evaluation of an ordinary\nJavaScript program\nmay return a value, may never terminate, or may signal an error.\n\nIn nondeterministic\nJavaScript\nthe evaluation of\na program\nmay in addition result in the discovery of\na dead end, in which case evaluation must backtrack to a previous choice\npoint.\n\nThe interpretation of nondeterministic\nJavaScript\nis complicated by this extra case.\n\nWe will construct the\nJavaScript\nby modifying the.\na component\nis accomplished by calling an\nfunction\nproduced by analysis of that\ncomponent.\n\nThe difference between the interpretation of ordinary\nJavaScript\nand the interpretation of nondeterministic\nJavaScript\nwill be entirely\nin the execution\nfunctions.\n\nRecall that the\nfunctions\nfor the ordinary evaluator take one argument: the environment of execution.\n\nIn contrast, the execution\nfunctions\nin the\nfunctions\ncalled\ncontinuation functions.\n\nThe evaluation of\na component\nwill finish by calling one of these two\ncontinuations: If the evaluation results in a value, the\nsuccess continuation is called with that value; if the evaluation\nresults in the discovery of a dead end, the\nfailure continuation is called.\n\nConstructing and calling\nappropriate continuations is the mechanism by which the nondeterministic\nevaluator implements backtracking.\n\nIt is the job of the success continuation to receive a value and proceed\nwith the computation.\n\nAlong with that value, the success continuation is\npassed another failure continuation, which is to be called subsequently if\nthe use of that value leads to a dead end.\n\nIt is the job of the failure continuation to try another branch of the\nnondeterministic process.\n\nThe essence of the nondeterministic\nlanguage is in the fact that\ncomponents\nmay represent choices among\nalternatives.\n\nThe evaluation of such\na component\nmust proceed with\none of the indicated alternative choices, even though it is not known\nin advance which choices will lead to acceptable results.", + "token_count": 298, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Implementing the amb Evaluator", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_1" + }, + { + "content": "The evaluation of such\na component\nmust proceed with\none of the indicated alternative choices, even though it is not known\nin advance which choices will lead to acceptable results.\n\nTo deal\nwith this, the evaluator picks one of the alternatives and passes this\nvalue to the success continuation.\n\nTogether with this value, the\nevaluator constructs and passes along a failure continuation that can\nbe called later to choose a different alternative.\n\nA failure is triggered during evaluation (that is, a failure\ncontinuation is called) when a user program explicitly rejects the\ncurrent line of attack (for example, a call to\namb(),\nan expression that always\nfails see section ).\n\nThe failure\ncontinuation in hand at that point will cause the most recent choice point\nto choose another alternative.\n\nIf there are no more alternatives to be\nconsidered at that choice point, a failure at an earlier choice point\nis triggered, and so on.\n\nFailure continuations are also invoked by\nthe driver loop in response to a\nretry\nrequest, to find another value of the\nprogram.\n\nIn addition, if a side-effect operation (such as assignment to a\nvariable) occurs on a branch of the process resulting from a choice,\nit may be necessary, when the process finds a dead end, to undo the\nside effect before making a new choice.\n\nThis is accomplished by\nhaving the side-effect operation produce a failure continuation that\nundoes the side effect and propagates the failure.\n\nIn summary, failure continuations are constructed by - - to provide a mechanism to make alternative choices if the current choice made by the -\n\n- the top-level driver to provide a mechanism to report failure when the choices are exhausted; - - assignments to intercept failures and undo assignments\n\nduring backtracking.", + "token_count": 293, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Implementing the amb Evaluator", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_2" + }, + { + "content": "during backtracking.\n\nFailures are initiated only when a dead end is encountered.\n\nThis occurs\n-\n-\nif the user program executes\n\n```javascript\namb();\n```\n\n- - if the user types retry at the top-level driver.\n\nFailure continuations are also called during processing of a failure: - - When the failure continuation created by an assignment finishes undoing a side effect,\n\nit calls the failure continuation it intercepted, in order to propagate the failure back to the choice point that led to this assignment or to\n\nthe top level. - - When the failure continuation for an\n\nThe syntax- and data-representation functions for the function, are identical to those in the evaluator of section , except for the fact that we need\n\nadditional syntax functions to recognize the amb syntactic form:\n\n```javascript\nis_amb_amb\n functions_4_1_2\n\nfunction is_amb(component) {\n return is_tagged_list(component, \"application\") &&\n is_name(function_expression(component)) &&\n symbol_of_name(function_expression(component)) === \"amb\";\n}\nfunction amb_choices(component) {\n return arg_expressions(component);\n}\n```\n\n```javascript\n\\newpage\\noindent\n\tWe continue to use the parse function of\n\tsection, which\n\tdoesn't support amb as a syntactic\n\tform and instead treats amb($\\ldots$) as\n\ta function application. The function\n\tis_amb ensures that\n\twhenever the name\n\tamb appears as the function\n\texpression of an application, the evaluator treats the\n\tapplication as\n\ta nondeterministic choice point.\n```\n\nWe must also add to the dispatch in\n\n```javascript\nsuch expressions and generate an appropriate execution\n function:\n```\n\n```javascript\nis_amb_case_amb\n all_solutions_test_5\n\n$\\ldots$\n: is_amb(component)\n? analyze_amb(component)\n: is_application(component)\n$\\ldots$\n```\n\nThe top-level function evaluate given in section ) analyzes the given component and applies the resulting execution function to the given environment, together with two\n\ngiven continuations:\n\n```javascript\nanalyze_amb_headline\n\n// functions from SICP JS 4.3.3\n```", + "token_count": 274, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Implementing the amb Evaluator", + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_3" + }, + { + "content": "given continuations:\n\n```javascript\nanalyze_amb\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n analyze_amb_headline\n is_amb_amb\n analyze_literal_amb\n analyze_variable_amb\n analyze_lambda_amb\n analyze_sequence_amb\n analyze_declaration_amb\n analyze_assignment_amb\n analyze_if_amb\n scan_out_declarations\n analyze_block_amb\n analyze_return_statement_amb\n analyze_application_amb\n analyze_amb_amb\n\nfunction analyze(component) {\n return is_literal(component)\n ? analyze_literal(component)\n : is_name(component)\n ? analyze_name(component)\n : is_amb(component)\n ? analyze_amb(component)\n : is_application(component)\n ? analyze_application(component)\n : is_operator_combination(component)\n ? analyze(operator_combination_to_application(component))\n : is_conditional(component)\n ? analyze_conditional(component)\n : is_lambda_expression(component)\n ? analyze_lambda_expression(component)\n : is_sequence(component)\n ? analyze_sequence(sequence_statements(component))\n : is_block(component)\n ? analyze_block(component)\n : is_return_statement(component)\n ? analyze_return_statement(component)\n : is_function_declaration(component)\n ? analyze(function_decl_to_constant_decl(component))\n : is_declaration(component)\n ? analyze_declaration(component)\n : is_assignment(component)\n ? analyze_assignment(component)\n : error(component, \"unknown syntax -- analyze\");\n}\n```\n\n```javascript\nambeval\n analyze_amb\n all_solutions_test_4\n\nfunction ambeval(component, env, succeed, fail) {\n return analyze(component)(env, succeed, fail);\n}\n```\n\nA success\nfunction\nof two arguments: the value just obtained and another failure continuation to\nbe used if that value leads to a subsequent failure.\n\nA\nfunction\nof no arguments.\n\nSo\nthe general form of an\nfunction\nis\n\n```javascript\n(env, succeed, fail) => {\n // $\\texttt{succeed}\\,$ is $\\texttt{(value, fail) =>}~\\ldots$\n // $\\texttt{fail}\\,$ is $\\texttt{() =>}~\\ldots$\n $\\ldots$\n}\n```\n\nFor example, executing\n\n```javascript\nambeval(component,\n the_global_environment,\n (value, fail) => value,\n () => \"failed\");\n```\n\nwill attempt to evaluate the given\ncomponent\nand will return either the\ncomponents\nvalue (if the evaluation succeeds) or the\nstring \"failed\"\n(if the evaluation fails).\n\nThe call to\nfunctions,\nwhich continue the loop and support the\nretry\nrequest.\n\nMost of the complexity of the\nfunctions\ncall each other.\n\nIn going through the following code, you should compare\neach of the execution\nfunctions\nwith the corresponding\nfunction\nfor the ordinary evaluator given in\nsection.\n\nThe execution\nfunctions\nfor the simplest kinds of expressions are\nessentially the same as those for the ordinary evaluator, except for the\nneed to manage the continuations.\n\nThe execution\nfunctions\nsimply succeed with the value of the expression, passing along the failure\ncontinuation that was passed to them.\n\n```javascript\nanalyze_literal_amb\n all_solutions_test_4\n\nfunction analyze_literal(component) {\n return (env, succeed, fail) =>\n succeed(literal_value(component), fail);\n}\n```", + "token_count": 309, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Implementing the amb Evaluator", + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_4" + }, + { + "content": "The execution\nfunctions\nsimply succeed with the value of the expression, passing along the failure\ncontinuation that was passed to them.\n\n```javascript\nanalyze_variable_amb\n all_solutions_test_4\n\nfunction analyze_name(component) {\n return (env, succeed, fail) =>\n succeed(lookup_symbol_value(symbol_of_name(component),\n env),\n fail);\n}\n```\n\n```javascript\nanalyze_lambda_amb\n all_solutions_test_4\n\nfunction analyze_lambda_expression(component) {\n const params = lambda_parameter_symbols(component);\n const bfun = analyze(lambda_body(component));\n return (env, succeed, fail) =>\n succeed(make_function(params, bfun, env),\n fail);\n}\n```\n\nNotice that looking up a\nname\nalways succeeds.\nlookup_symbol_value\nfails to find the\nname,\nit signals an\nerror, as usual.\n\nSuch a failure indicates a program\nbug a reference to an unbound\n\n```javascript\nname;\n```\n\nit is not an indication that we should try another nondeterministic choice instead of the one that is currently being tried.\n\nConditionals are also handled in a similar way as in the ordinary\nevaluator.\n\nThe execution\nfunction\ngenerated by\nanalyze_conditional\ninvokes the predicate execution\n\n```javascript\nfunction\n pfun\n```\n\nwith a success continuation that checks whether the predicate value is true\nand goes on to execute either the consequent or the alternative.\n\nIf the\nexecution of\npfun\nfails, the original failure continuation for\nthe\nconditional\nexpression is called.\n\n```javascript\nanalyze_if_amb\n all_solutions_test_4\n\nfunction analyze_conditional(component) {\n const pfun = analyze(conditional_predicate(component));\n const cfun = analyze(conditional_consequent(component));\n const afun = analyze(conditional_alternative(component));\n return (env, succeed, fail) =>\n pfun(env,\n // success continuation for evaluating the predicate\n // to obtain $\\texttt{pred\\char`_value}$\n (pred_value, fail2) =>\n is_truthy(pred_value)\n ? cfun(env, succeed, fail2)\n : afun(env, succeed, fail2),\n // failure continuation for evaluating the predicate\n fail);\n}\n```", + "token_count": 241, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Implementing the amb Evaluator", + "chunk_index": 5, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_5" + }, + { + "content": "If the\nexecution of\npfun\nfails, the original failure continuation for\nthe\nconditional\nexpression is called.\n\n```javascript\nSequences are also handled in the same way as in the previous\n\tevaluator, except for the machinations in the\n\tsubfunction\n\n\t analyze_sequence_amb\n\t all_solutions_test_4\n\nfunction analyze_sequence(stmts) {\n function sequentially(a, b) {\n return (env, succeed, fail) =>\n a(env,\n // success continuation for calling $\\texttt{a}$\n (a_value, fail2) =>\n is_return_value(a_value)\n ? succeed(a_value, fail2)\n : b(env, succeed, fail2),\n // failure continuation for calling $\\texttt{a}$\n fail);\n }\n function loop(first_fun, rest_funs) {\n return is_null(rest_funs)\n ? first_fun\n : loop(sequentially(first_fun, head(rest_funs)),\n tail(rest_funs));\n }\n const funs = map(analyze, stmts);\n return is_null(funs)\n ? env => undefined\n : loop(head(funs), tail(funs));\n}\n```\n\nDeclarations\nare another case where we must go to some trouble to\nmanage the continuations, because it is necessary to evaluate the\ndeclaration-value expression before actually declaring the new name.\n\nTo accomplish this, the\ndeclaration-value\nexecution\nfunction\nis called with the environment, a success continuation, and the\nfailure continuation.\n\nIf the execution of\nsucceeds, obtaining a value\ndeclared name, the name is declared and the success is propagated:\n\n```javascript\nanalyze_declaration_amb\n all_solutions_test_4\n\nfunction analyze_declaration(component) {\n const symbol = declaration_symbol(component);\n const vfun = analyze(declaration_value_expression(component));\n return (env, succeed, fail) =>\n vfun(env,\n (val, fail2) => {\n assign_symbol_value(symbol, val, env);\n return succeed(undefined, fail2);\n },\n fail);\n}\n```\n\nAssignments\nfunction\nfor assignments starts out like the one for\ndeclarations.\n\nIt first attempts\nto obtain the new value to be assigned to the\nname.\n\nIf this evaluation of\nvfun\nfails, the assignment fails.\n\nIf\nvfun\nsucceeds, however, and we go on to make the assignment, we must consider the\npossibility that this branch of the computation might later fail, which will\nrequire us to backtrack out of the assignment.\n\nThus, we must arrange to\nundo the assignment as part of the backtracking process.", + "token_count": 292, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Implementing the amb Evaluator", + "chunk_index": 6, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_6" + }, + { + "content": "Thus, we must arrange to\nundo the assignment as part of the backtracking process.\n\nThis is accomplished by giving\nvfun\na success continuation (marked with the comment *1* below)\nthat saves the old value of the variable before assigning the new value to\nthe variable and proceeding from the assignment.\n\nThe failure continuation\nthat is passed along with the value of the assignment (marked with the\ncomment *2* below) restores the old value of the variable\nbefore continuing the failure.\n\nThat is, a successful assignment provides a\nfailure continuation that will intercept a subsequent failure; whatever\nfailure would otherwise have called\nfunction\ninstead, to undo the assignment before actually calling\n\n```javascript\nanalyze_assignment_amb\n all_solutions_test_4\n\nfunction analyze_assignment(component) {\n const symbol = assignment_symbol(component);\n const vfun = analyze(assignment_value_expression(component));\n return (env, succeed, fail) =>\n vfun(env,\n (val, fail2) => { // *1*\n const old_value = lookup_symbol_value(symbol,\n env);\n assign_symbol_value(symbol, val, env);\n return succeed(val,\n () => { // *2*\n assign_symbol_value(symbol,\n old_value,\n env);\n return fail2();\n });\n },\n fail);\n}\n```\n\nAnalyzing return statements is straightforward.\n\nThe return expression is analyzed to produce an execution function.\n\nThe execution function for the return statement calls that execution\nfunction with a success continuation that wraps the return value\nin a return value object and passes it to the original success continuation.\n\n```javascript\nanalyze_return_statement_amb\n\t all_solutions_test_4\n\nfunction analyze_return_statement(component) {\n const rfun = analyze(return_expression(component));\n return (env, succeed, fail) =>\n rfun(env,\n (val, fail2) =>\n succeed(make_return_value(val), fail2),\n fail);\n}\n```\n\nThe execution function for blocks calls the body s execution function on an extended environment, without changing success or failure continuations.\n\n```javascript\nanalyze_block_amb\n\t list_of_unassigned\n\t all_solutions_test_4\n\nfunction analyze_block(component) {\n const body = block_body(component);\n const locals = scan_out_declarations(body);\n const unassigneds = list_of_unassigned(locals);\n const bfun = analyze(body);\n return (env, succeed, fail) =>\n bfun(extend_environment(locals, unassigneds, env),\n succeed,\n fail);\n}\n```", + "token_count": 289, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Implementing the amb Evaluator", + "chunk_index": 7, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_7" + }, + { + "content": "The execution function for blocks calls the body s execution function on an extended environment, without changing success or failure continuations.\n\nThe execution\nfunction\nfor applications contains no new ideas except for the technical complexity\nof managing the continuations.\n\nThis complexity arises in\nanalyze_@application,\ndue to the need to keep track of the success and failure continuations as\nwe evaluate the\nargument expressions.\n\nWe use a\nfunction get_args\nto evaluate the list of\nargument expressions,\nrather than a simple\n\n```javascript\nanalyze_application_amb\n get_args_amb\n execute_application_amb\n all_solutions_test_4\n\nfunction analyze_application(component) {\n const ffun = analyze(function_expression(component));\n const afuns = map(analyze, arg_expressions(component));\n return (env, succeed, fail) =>\n ffun(env,\n (fun, fail2) =>\n get_args(afuns,\n env,\n (args, fail3) =>\n execute_application(fun,\n args,\n succeed,\n fail3),\n fail2),\n fail);\n}\n```\n\n```javascript\nIn get_args,\n\tnotice how walking down the list of\n\tafun\n\texecution functions\n\tand constructing the resulting list of\n\tafun\n\tin the list with a success continuation that recursively calls\n\tget_args.\n```\n\nEach of these recursive calls to get_args has a success continuation whose value is the\n\n```javascript\nnew list resulting from using\n\tpair\n\tto adjoin the newly obtained argument\n\tto the list of accumulated arguments:\n```\n\n```javascript\nget_args_amb\n all_solutions_test_4\n\nfunction get_args(afuns, env, succeed, fail) {\n return is_null(afuns)\n ? succeed(null, fail)\n : head(afuns)(env,\n // success continuation for this $\\texttt{afun}$\n (arg, fail2) =>\n get_args(tail(afuns),\n env,\n // success continuation for\n // recursive call to $\\texttt{get\\char`_args}$\n (args, fail3) =>\n succeed(pair(arg, args),\n fail3),\n fail2),\n fail);\n}\n```\n\nThe actual function application, which is performed by execute_application, is accomplished in the same way as for the ordinary evaluator, except for the need to\n\nmanage the continuations.\n\n```javascript\nexecute_application_amb\n all_solutions_test_4\n\nfunction execute_application(fun, args, succeed, fail) {\n return is_primitive_function(fun)\n ? succeed(apply_primitive_function(fun, args),\n fail)\n : is_compound_function(fun)\n ? function_body(fun)(\n extend_environment(function_parameters(fun),\n args,\n function_environment(fun)),\n (body_result, fail2) =>\n succeed(is_return_value(body_result)\n ? return_value_content(body_result)\n : undefined,\n fail2),\n fail)\n : error(fun, \"unknown function type - execute_application\");\n}\n```", + "token_count": 300, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Implementing the amb Evaluator", + "chunk_index": 8, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_8" + }, + { + "content": "manage the continuations.\n\nThe\nsyntactic\nform is the key element in the nondeterministic language.\n\nHere we see the\nessence of the interpretation process and the reason for keeping track of\nthe continuations.\n\nThe execution\nfunction\nfor\ntry_next\nthat cycles through the execution\nfunctions\nfor all the possible values of the\nfunction\nis called with a\n\n```javascript\nanalyze_amb_amb\n all_solutions_test_4\n\nfunction analyze_amb(component) {\n const cfuns = map(analyze, amb_choices(component));\n return (env, succeed, fail) => {\n function try_next(choices) {\n return is_null(choices)\n ? fail()\n : head(choices)(env,\n succeed,\n () =>\n try_next(tail(choices)));\n }\n return try_next(cfuns);\n };\n}\n```\n\nThe driver loop for the\na program.\n\nThe driver uses a\nfunction\ncalled\ninternal_loop,\nwhich takes as argument a\nfunction\nretry.\n\nThe intent is that calling\nretry\nshould go on to the next untried alternative in the nondeterministic\nevaluation.\n\n```javascript\nThe function\n internal_loop\n```\n\neither calls retry in response to the user typing retry at the driver loop, or else starts a new evaluation by calling\n\nThe failure continuation for this call to\n\nThe success continuation for the call to\nreinvoke the internal loop\nwith a\nretry\nfunction\nthat will be able to try the next alternative.\n\nThis\nnext_alternative\nfunction\nis the second argument that was passed to the success continuation.\n\nOrdinarily, we think of this second argument as a failure continuation to\nbe used if the current evaluation branch later fails.\n\nIn this case,\nhowever, we have completed a successful evaluation, so we can invoke the\nfailure alternative branch in order to search for additional\nsuccessful evaluations.\n\n```javascript\ndriver_loop_amb_example\n\ndriver_loop();\n```", + "token_count": 253, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Implementing the amb Evaluator", + "chunk_index": 9, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_9" + }, + { + "content": "In this case,\nhowever, we have completed a successful evaluation, so we can invoke the\nfailure alternative branch in order to search for additional\nsuccessful evaluations.\n\n```javascript\ndriver_loop_amb\n ambeval\n user_print\n user_read\n driver_loop_amb_example\n\nconst input_prompt = \"amb-evaluate input:\";\nconst output_prompt = \"amb-evaluate value:\";\n\nfunction driver_loop(env) {\n function internal_loop(retry) {\n const input = user_read(input_prompt);\n if (is_null(input)) {\n display(\"evaluator terminated\");\n } else if (input === \"retry\") {\n return retry();\n } else {\n display(\"Starting a new problem\");\n const program = parse(input);\n const locals = scan_out_declarations(program);\n const unassigneds = list_of_unassigned(locals);\n const program_env = extend_environment(\n locals, unassigneds, env);\n return ambeval(\n program,\n program_env,\n // ambeval success\n (val, next_alternative) => {\n user_print(output_prompt, val);\n return internal_loop(next_alternative);\n },\n // ambeval failure\n () => {\n display(\"There are no more values of\");\n display(input);\n return driver_loop(program_env);\n });\n }\n }\n return internal_loop(() => {\n display(\"There is no current problem\");\n return driver_loop(env);\n });\n}\n\nconst input_prompt = \"amb-evaluate input:\";\nconst output_prompt = \"amb-evaluate value:\";\nfunction driver_loop() {\n function internal_loop(retry) {\n const input = user_read(input_prompt);\n if (is_null(input)) {\n display(\"--- evaluator terminated ---\", \"\");\n } else if (input === \"retry\") {\n display(\"----------------------------\",\n input_prompt + \"\\n\" + input + \"\\n\");\n return retry();\n } else {\n display(\"--- starting new problem ---\",\n input_prompt + \"\\n\" + input + \"\\n\");\n ambeval(parse(\"{ \" + input + \" }\"),\n the_global_environment,\n // ambeval success\n (val, next_alternative) => {\n user_print(output_prompt, val);\n return internal_loop(next_alternative);\n },\n // ambeval failure\n () => {\n display(\"----------------------------\",\n \"no more values of:\\n\" + input + \"\\n\");\n return driver_loop();\n });\n }\n }\n return internal_loop(\n () => {\n display(\"--- no current problem ---\", \"\");\n return driver_loop();\n });\n}\n```\n\nThe initial call to\ninternal_loop\nuses a\nretry\nfunction\nthat complains that there is no current problem and restarts the driver loop.\n\nThis is the behavior that will happen if the user types\nretry\nwhen there is no evaluation in progress.", + "token_count": 296, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Implementing the amb Evaluator", + "chunk_index": 10, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_10" + }, + { + "content": "This is the behavior that will happen if the user types\nretry\nwhen there is no evaluation in progress.\n\nWe start the driver loop as usual, by setting up the global environment and passing it as the enclosing environment for the first iteration of\n\ndriver_loop.\n\n```javascript\nconst the_global_environment = setup_environment();\ndriver_loop(the_global_environment);\n```", + "token_count": 52, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Implementing the amb Evaluator", + "chunk_index": 11, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_11" + }, + { + "content": "To extend\nJavaScript\nto support nondeterminism, we introduce a new\nsyntactic form\namb($e_1,\\ e_2,\\ldots, e_n$)\nreturns the value of one of the $n$ expressions\n$e_i$ ambiguously.\n\nFor example,\nthe expression\n\n```javascript\nlist_non_det\n\nlist(amb(1, 2, 3), amb(\"a\", \"b\"));\n\nlist(amb(1, 2, 3), amb(\"a\", \"b\"));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\ncan have six possible values:\n\n```javascript\nlist(1, \"a\") list(1, \"b\") list(2, \"a\")\nlist(2, \"b\") list(3, \"a\") list(3, \"b\")\n```\n\nAn amb expression with a single choice produces an ordinary (single) value.\n\nAn amb expression\nwith no choices the expression\namb()is\namb()\nas an expression that when evaluated causes the computation to\nfail : The computation aborts and no value is produced.\n\nUsing this idea, we can express the requirement that a particular predicate\nexpression\n\n```javascript\nrequire_non_det_example\n\nconst x = amb(1, 3, 5, 7, 9);\nrequire(x >= 4);\nx;\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\nrequire_non_det\n require_non_det_example\n 5\n\nfunction require(p) {\n if (! p) {\n amb();\n } else {}\n}\n```\n\nWith an_element_of function used above:\n\n```javascript\nan_element_of_example\n\nconst xs = list(\"apple\", \"banana\", \"cranberry\");\nan_element_of(xs);\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\nan_element_of\n an_element_of_example\n 'apple'\n\nfunction an_element_of(items) {\n require(! is_null(items));\n return amb(head(items), an_element_of(tail(items)));\n}\n```\n\nAn application of an_element_of\nfails if the list is empty.\n\nOtherwise it ambiguously returns either the\nfirst element of the list or an element chosen from the rest of the list.\n\nWe can also express infinite ranges of choices.\n\nThe following\nfunction\npotentially returns any integer greater than or equal to some\ngiven $n$ :", + "token_count": 293, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Search and amb", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Search_and_amb_1" + }, + { + "content": "The following\nfunction\npotentially returns any integer greater than or equal to some\ngiven $n$ :\n\n```javascript\nan_integer_starting_from_example\n\nconst x = an_integer_starting_from(1);\nrequire(x >= 4.5);\nx;\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\nan_integer_starting_from\n an_integer_starting_from_example\n 5\n\nfunction an_integer_starting_from(n) {\n return amb(n, an_integer_starting_from(n + 1));\n}\n```\n\nThis is like the stream\n\n```javascript\nfunction\n integers_starting_from\n```\n\ndescribed in section , but with an important difference: The stream function returns an object that represents the sequence of all integers beginning with $n$\n\n, whereas the function returns a single integer.\n\nAbstractly, we can imagine that evaluating an\nnondeterministic choice point.\n\nIf we had a machine with a\nsufficient number of processors that could be dynamically allocated, we\ncould implement the search in a straightforward way.\n\nExecution would\nproceed as in a sequential machine, until an\n\nOn the other hand, if we have a machine that can execute only one process\n(or a few concurrent processes), we must consider the alternatives\nsystematically search all possible execution paths.\n\nThe\nbacktracks to the most recent choice point and tries the next\nalternative.\n\nIf it runs out of alternatives at any choice point, the\nevaluator will back up to the previous choice point and resume from there.\n\nThis process leads to a search strategy known as\ndepth-first search or\nchronological\nbacktracking.\n\nThe\na program\nand prints the value of the\nfirst non-failing execution, as in the\nprime_sum_pair\nexample shown above.\n\nIf we want to see the value of the next successful\nexecution, we can ask the interpreter to backtrack and attempt to generate a\nsecond non-failing execution.\n\n```javascript\nThis is signaled by typing\n\tretry.\n\tIf any other input except retry\n\tis given, the interpreter will start a new problem, discarding the\n\tunexplored alternatives in the previous problem.\n```", + "token_count": 307, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Search and amb", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Search_and_amb_2" + }, + { + "content": "If we want to see the value of the next successful\nexecution, we can ask the interpreter to backtrack and attempt to generate a\nsecond non-failing execution.\n\nHere is a sample interaction:\n\n```javascript\ninteraction_non_det\n is_prime2\n prime_sum_pair_non_det\n\namb-evaluate input:\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\ninteraction_non_det_2\n is_prime2\n prime_sum_pair_non_det\n\namb-evaluate input:\n\nretry\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\ninteraction_non_det_3\n is_prime2\n prime_sum_pair_non_det\n\namb-evaluate input:\n\nretry\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\ninteraction_non_det_4\n is_prime2\n prime_sum_pair_non_det\n\namb-evaluate input:\n\nretry\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\ninteraction_non_det_5\n is_prime2\n prime_sum_pair_non_det\n\namb-evaluate input:\n\nprime_sum_pair(list(19, 27, 30), list(11, 36, 58));\n\nprime_sum_pair(list(19, 27, 30), list(11, 36, 58));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```", + "token_count": 217, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Search and amb", + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_Search_and_amb_3" + }, + { + "content": "Our evaluator for\nJavaScript\nwill be implemented as a\nJavaScript\nprogram.\n\nIt may\nseem circular to think about evaluating\nJavaScript\nprograms using an evaluator that is itself implemented in\nJavaScript.\n\nHowever, evaluation is a process, so it is appropriate to describe the\nevaluation process using\nJavaScript,\nwhich, after all, is our tool for describing processes. metacircular.\n\nThe metacircular evaluator is essentially a JavaScript formulation of the.\n\n```javascript\nRecall that the model specifies the\n\tevaluation of function application in two basic steps:\n```\n\n- -\n\n```javascript\nTo evaluate a function application, evaluate the\n\t subexpressions and then apply the\n\t value of the function subexpression to the values of the argument\n subexpressions.\n```\n\n-\n-\nTo apply a compound\nfunction\nto a set of arguments, evaluate the\nbody of the\nfunction\nin a new environment.\n\nTo construct this\nenvironment, extend the environment part of the\nfunction\nobject by a\nframe in which the\nparameters of the\nfunction\nare bound to\nthe arguments to which the\nfunction\nis applied.\n\nThese two rules describe the essence of the evaluation process, a basic cycle in which statements and expressions to be evaluated in environments are reduced\n\nto functions to be applied to arguments, which in turn are reduced to new statements and expressions to be evaluated in new environments, and so\n\non, until we get down to names, whose values are looked up in the environment, and to operators and primitive functions, which are applied directly\n\n(see figure ). functions in the evaluator, and (see figure ).", + "token_count": 251, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_The_Metacircular_Evaluator_1" + }, + { + "content": "(see figure ). functions in the evaluator, and (see figure ).\n\n```javascript\nThe implementation of the evaluator will depend upon functions that\n\tdefine the syntax of the statements and\n\texpressions to be evaluated.\n\tWe will use\n\t=, we use an abstract predicate\n\tis_assignment to test for an\n\tassignment, and we use abstract\n\tselectors assignment_symbol and\n\tassignment_value_expression to\n\taccess the parts of an assignment.\n\tThe data abstraction layers presented in\n\tsection\n\twill allow the evaluator to remain independent of concrete syntactic\n\tissues, such as the keywords of the interpreted language, and of the\n\tchoice of data structures that represent the program components.\n```\n\nThere are also\noperations, described in\nsection , that specify the\nrepresentation of\nfunctions\nand environments.\n\nFor example,\nmake_function\nconstructs compound\nfunctions,\nlookup_symbol_value\naccesses the values of\nnames,\nand\napply_primitive_function\napplies a primitive\nfunction\nto a given list of arguments.", + "token_count": 142, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_The_Metacircular_Evaluator_2" + }, + { + "content": "In thinking about a\nJavaScript\nprogram that evaluates\nJavaScript statements and\nexpressions, an analogy might be helpful.\n\nOne operational view of the\nmeaning of a program is that a\n\n```javascript\nfactorial_4_1_5\n factorial_example\n 120\n\nfunction factorial(n) {\n return n === 1\n ? 1\n : factorial(n - 1) * n;\n}\n```\n\nWe may regard this program as the description of a is a flow diagram for the factorial machine, showing how the parts are wired together.\n\nIn a similar way, we can regard the evaluator as a very special\nfigure,\nthe evaluator will be able to compute factorials.\n\nThe evaluator emulating a factorial machine.\n\nFrom this perspective, our evaluator is seen to be a\nuniversal machine.\n\nIt mimics other machines when these are described as\nJavaScript\nprograms.\n\nAnother striking aspect of the evaluator is that it acts as a bridge between\nthe data objects that are manipulated by our programming language and the\nprogramming language itself.\n\nImagine that the evaluator program\n(implemented in JavaScript)\nis running, and that a user is typing\nprograms\nto the evaluator and\nobserving the results.\n\nFrom the perspective of the user, an input\nprogram\nsuch as\n\n```javascript\nx * x;\n```\n\nis a program in the programming language, which the evaluator should execute.\n\n```javascript\nFrom the perspective of the evaluator, however, the program is simply\n\ta string orafter parsinga tagged-list representation\n\tthat is to be manipulated according to a well-defined set of rules.\n```\n\nThat the\ns programs are the evaluator s data need not\nbe a source of confusion.\n\nIn fact, it is sometimes convenient to ignore\nthis distinction, and to give the user the ability to explicitly\nevaluate a string as a JavaScript statement, using JavaScript's\nprimitive function provided that it is syntactically correct evaluates the\nresulting representation in the environment in which\n\n```javascript\neval(\"5 * 5;\");\n```\n\nand", + "token_count": 305, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Data as Programs", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Data_as_Programs_1" + }, + { + "content": "and\n\n```javascript\nevaluate_example_4_1_5\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\t 25\n\nevaluate(parse(\"5 * 5;\"), the_global_environment);\n```\n\nwill both return 25.", + "token_count": 17, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Data as Programs", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Data_as_Programs_2" + }, + { + "content": "In addition to defining the representation of components, the evaluator implementation must also define the data structures that the evaluator manipulates internally, as part of\n\nthe execution of a program, such as the representation of functions and environments and the representation of true and false.\n\n```javascript\nIn order to limit the predicate expressions of conditionals to proper\n\tpredicates (expressions that evaluate to a boolean value) as we do throughout\n\tthis book, we insist here that the function\n\tis_truthy gets applied only to\n\tboolean values, and we accept only the boolean value\n\ttrue to be truthy.\n The opposite of\n is_truthy is called\n is_falsy.\n```\n\n```javascript\nheadline_4_1_3\n\n// functions from SICP JS 4.1.3\n```\n\n```javascript\ntrue\n true_example\n false\n\nfunction is_truthy(x) {\n return is_boolean(x)\n ? x\n : error(x, \"boolean expected, received\");\n}\nfunction is_falsy(x) { return ! is_truthy(x); }\n```\n\n```javascript\ntrue_example\n\nis_truthy(false); // should return false because only true is truthy\n```\n\nTo handle primitives, we assume that we have available the following\nfunctions:\n-\n-\napply_primitive_function(fun, args)\napplies the given primitive\nfunction\nto the argument values in the list args and returns the result of\nthe application.\n-\n-\nis_primitive_function(fun)\ntests whether\nfun\nis a primitive\nfunction.\n\nThese mechanisms for handling primitives are further described in\nsection.\n\nCompound functions are constructed from parameters, function bodies, and environments using the constructor make_function:\n\n```javascript\nmake_procedure\n tagged_list\n make_procedure_example\n [ 'x', [ 'y', null ] ]\n\nfunction make_function(parameters, body, env) {\n return list(\"compound_function\", parameters, body, env);\n}\nfunction is_compound_function(f) {\n return is_tagged_list(f, \"compound_function\");\n}\nfunction function_parameters(f) { return list_ref(f, 1); }\n\nfunction function_body(f) { return list_ref(f, 2); }\n\nfunction function_environment(f) { return list_ref(f, 3); }\n```\n\n```javascript\nmake_procedure_example\n enclosing_environment\n\nconst my_function =\n make_function(\n list(\"x\", \"y\"),\n list(\"return_statement\", parse(\"x + y;\")),\n the_empty_environment);\ndisplay(is_compound_function(my_function));\ndisplay(function_parameters(my_function));\ndisplay(function_body(my_function));\ndisplay(function_environment(my_function));\n\nconst my_function =\n make_function(\n list(\"x\", \"y\"),\n list(\"return_statement\", parse(\"x + y;\")),\n the_empty_environment);\nis_compound_function(my_function);\nfunction_body(my_function);\nfunction_environment(my_function);\nfunction_parameters(my_function);\n```", + "token_count": 305, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Evaluator Data Structures", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Evaluator_Data_Structures_1" + }, + { + "content": "Compound functions are constructed from parameters, function bodies, and environments using the constructor make_function:\n\nWe saw in section that the\nevaluation of a sequence terminates when a return statement\nis encountered, and that the evaluation of a function application needs\nto return the value undefined if\nthe evaluation of the function body does not encounter a\nreturn statement.\n\nIn order to recognize that a value resulted from a\nreturn values as evaluator data\nstructures.\n\n```javascript\nreturn_value\n\t tagged_list\n\t return_value_example\n\t 42\n\nfunction make_return_value(content) {\n return list(\"return_value\", content);\n}\nfunction is_return_value(value) {\n return is_tagged_list(value, \"return_value\");\n}\nfunction return_value_content(value) {\n return head(tail(value));\n}\n```\n\n```javascript\nreturn_value_example\n\t enclosing_environment\n\nconst my_return_value = make_return_value(42);\ndisplay(is_return_value(my_return_value));\ndisplay(return_value_content(my_return_value));\n\nconst my_return_value = make_return_value(42);\nis_return_value(my_return_value);\nreturn_value_content(my_return_value);\n```\n\nThe evaluator needs operations for\n, an environment is a\nsequence of frames, where each frame is a table of bindings that associate\nsymbols\nwith their corresponding values.\n\nWe use the following operations for\nmanipulating environments:\n\n```javascript\nlookup_symbol_value(symbol, env)\n\n\t returns the value that is bound to\n\t symbol in the environment\n\t env, or signals an error if\n\t symbol is unbound.\n\n\t extend_environment(symbols, values, base-env)\n\n\t returns a new environment, consisting of a new frame in which the\n\t symbols in the list symbols\n\t are bound to the corresponding elements in the list\n\t values, where the enclosing\n\t environment is the environment\n\t base-env.\n\n\t assign_symbol_value(symbol, value, env)\n\n\t finds the innermost frame of\n\t env\n\t in which symbol\n\t is bound, and changes that frame\n\t so that\n\t symbol\n\t is now bound to\n\t value, or signals an\n\t error if symbol is\n\t unbound.\n```\n\nTo implement these operations we\ntail\nof the list.\n\nThe empty environment is simply the empty list.\n\n```javascript\nenclosing_environment\n enclosing_environment_example\n null\n\nfunction enclosing_environment(env) { return tail(env); }\n\nfunction first_frame(env) { return head(env); }\n\nconst the_empty_environment = null;\n```\n\n```javascript\nenclosing_environment_example\n\nthe_empty_environment;\n```", + "token_count": 290, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Evaluator Data Structures", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Evaluator_Data_Structures_2" + }, + { + "content": "The empty environment is simply the empty list.\n\nEach frame of an environment is represented as a pair of lists: a list of the names bound in that frame and a list of\n\nthe associated values.\n\n```javascript\nmake_frame_example\n\nconst my_frame = make_frame(list(\"x\", \"y\"), list(1, 2));\nframe_symbols(my_frame);\n```\n\n```javascript\nmake_frame\n make_frame_example\n [ 'x', [ 'y', null ] ]\n\nfunction make_frame(symbols, values) { return pair(symbols, values); }\n\nfunction frame_symbols(frame) { return head(frame); }\n\nfunction frame_values(frame) { return tail(frame); }\n```\n\nTo extend an environment by a new frame that associates\nsymbols\nwith values, we make a frame consisting of the list of\nsymbols\nand the list of values, and we adjoin this to the environment.\n\nWe signal\nan error if the number of\nsymbols\ndoes not match the number of values.\n\n```javascript\nextend_environment\n extend_environment_example\n [ 1, [ 2, [ 3, null ] ] ]\n\nfunction extend_environment(symbols, vals, base_env) {\n return length(symbols) === length(vals)\n ? pair(make_frame(symbols, vals), base_env)\n : error(pair(symbols, vals),\n length(symbols) < length(vals)\n ? \"too many arguments supplied\"\n : \"too few arguments supplied\");\n}\n\nfunction extend_environment(symbols, vals, base_env) {\n return length(symbols) === length(vals)\n ? pair(make_frame(symbols, vals), base_env)\n : length(symbols) < length(vals)\n ? error(\"too many arguments supplied: \" +\n stringify(symbols) + \", \" +\n stringify(vals))\n : error(\"too few arguments supplied: \" +\n stringify(symbols) + \", \" +\n stringify(vals));\n}\n```\n\n```javascript\nextend_environment_example\n enclosing_environment\n make_frame\n\nextend_environment(list(\"x\", \"y\", \"z\"),\n list(1, 2, 3),\n the_empty_environment);\n\ntail(head(extend_environment(list(\"x\", \"y\", \"z\"),\n list(1, 2, 3),\n the_empty_environment)));\n```\n\n```javascript\nThis is used by apply in\n\tsection to bind the\n\tparameters of a function to its arguments.\n```\n\nTo look up a\nsymbol\nin an environment, we scan the list of\nsymbols\nin the first frame.\n\nIf we find the desired\nsymbol,\nwe return the corresponding element in the list of values.\n\nIf we do not\nfind the\nsymbol\nin the current frame, we search the enclosing environment, and so on.", + "token_count": 308, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Evaluator Data Structures", + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_Evaluator_Data_Structures_3" + }, + { + "content": "If we do not\nfind the\nsymbol\nin the current frame, we search the enclosing environment, and so on.\n\nIf we reach the empty environment, we signal an\n\"unbound name\"\nerror.\n\n```javascript\nlookup_variable_value\n lookup_variable_value_example\n 1\n\nfunction lookup_symbol_value(symbol, env) {\n function env_loop(env) {\n function scan(symbols, vals) {\n return is_null(symbols)\n ? env_loop(enclosing_environment(env))\n : symbol === head(symbols)\n ? head(vals)\n : scan(tail(symbols), tail(vals));\n }\n if (env === the_empty_environment) {\n error(symbol, \"unbound name\");\n } else {\n const frame = first_frame(env);\n return scan(frame_symbols(frame), frame_values(frame));\n }\n }\n return env_loop(env);\n}\n```\n\n```javascript\nlookup_variable_value_example\n enclosing_environment\n extend_environment\n make_frame\n\nconst my_environment =\n extend_environment(list(\"x\", \"y\", \"z\"),\n list(1, 2, 3),\n the_empty_environment);\n\nlookup_symbol_value(\"x\", my_environment);\n```\n\n```javascript\nTo assign\n a new value to a symbol in a specified environment, we scan\n for the symbol, just as in\n lookup_symbol_value,\n and change the corresponding value when we find it.\n```\n\n```javascript\nassign_name_value\n assign_name_value_example\n 2\n\nfunction assign_symbol_value(symbol, val, env) {\n function env_loop(env) {\n function scan(symbols, vals) {\n return is_null(symbols)\n ? env_loop(enclosing_environment(env))\n : symbol === head(symbols)\n ? set_head(vals, val)\n : scan(tail(symbols), tail(vals));\n }\n if (env === the_empty_environment) {\n error(symbol, \"unbound name -- assignment\");\n } else {\n const frame = first_frame(env);\n return scan(frame_symbols(frame), frame_values(frame));\n }\n }\n return env_loop(env);\n}\n```\n\n```javascript\nassign_name_value_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_block = parse(\"{ let x = 1; x = 2; }\");\nevaluate(my_block, the_global_environment);\n```\n\nThe method described here is only one of many plausible ways to represent\nenvironments.\n\nSince we used.) In a\nproduction-quality\nJavaScript\nsystem, the speed of the evaluator s environment\noperations especially that of\nsymbol\nlookup has a major\nimpact on the performance of the system.\n\nThe representation described here,\nalthough conceptually simple, is not efficient and would not ordinarily be\nused in a production system.", + "token_count": 277, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Evaluator Data Structures", + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_Evaluator_Data_Structures_4" + }, + { + "content": "In JavaScript, the scope of a declaration\nis the entire block that immediately surrounds the declaration,\nnot just the portion of the block starting at the point where\nthe declaration occurs.\n\nThis section takes a closer look at this design choice.\n\nLet us revisit the pair of mutually recursive functions is_even and is_odd from Section , declared locally in the body of a function f.\n\n```javascript\nf_is_even_is_odd_2\n\nfunction f(x) {\n function is_even(n) {\n return n === 0\n ? true\n : is_odd(n - 1);\n }\n function is_odd(n) {\n return n === 0\n ? false\n : is_even(n - 1);\n }\n return is_even(x);\n}\n```\n\nOur intention here is that the name\nis_odd\nin the body of the function is_even\nshould refer to the function is_odd\nthat is declared after is_even.\n\nThe scope of the name is_odd is the\nentire body block of is_odd\noccurs.\n\nIndeed, when we consider that\nis_odd is itself defined in terms of\nis_even so that\nis_even and\nis_odd are mutually recursive\nfunctions we see that the only satisfactory interpretation of\nthe two declarations is to regard them as if the names\nis_even and\nis_odd\nwere being added to the environment simultaneously.\n\nMore generally, in\nblock structure, the scope of a local name is the entire block\nin which the declaration is evaluated.\n\nThe evaluation of blocks in the metacircular evaluator of\nsection achieves such\na simultaneous scope for local names by\nis_even and\nis_odd , and any occurrence\nof one of these names refers to the correct binding.\n\nOnce their\ndeclarations are evaluated,\nthese names are bound to their declared values, namely function\nobjects that have the extended environment as their environment\npart.", + "token_count": 275, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Internal Declarations", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Internal_Declarations_1" + }, + { + "content": "Once their\ndeclarations are evaluated,\nthese names are bound to their declared values, namely function\nobjects that have the extended environment as their environment\npart.\n\nThus, for example,\nby the time\nis_even\ngets applied in the body of\nf , its environment\nalready contains the correct binding for the symbol\nis_odd , and\nthe evaluation of the name\nis_odd in the body of\nis_even retrieves the correct\nvalue.\n\nimposes a\nruntime burden on the evaluation of blocks: It needs to scan\nthe body of the block for locally declared names, extend the\ncurrent environment with a new frame that binds those names, and evaluate the\nblock body in this extended environment.\n\nAlternatively, the evaluation\nof a block could extend the current environment with an empty frame.\n\nThe evaluation of each declaration in the block body would then add\na new binding to that frame.\n\nTo implement this design, we first simplify\neval_block :\n\n```javascript\neval_block_simplified\n\nfunction eval_block(component, env) {\n const body = block_body(component);\n return evaluate(body, extend_environment(null, null, env);\n}\n```\n\nThe function\neval_declaration can no\nlonger assume that the environment already has a binding for\nthe name.\n\nInstead of using\nassign_symbol_value to\nchange an existing binding, it calls a new function,\nadd_binding_to_frame , to\nadd to the first frame of the environment a binding of the name\nto the value of the value expression.\n\n```javascript\neval_declaration_simplified\n\nfunction eval_declaration(component, env) {\n add_binding_to_frame(\n declaration_symbol(component),\n evaluate(declaration_value_expression(component), env),\n first_frame(env));\n return undefined;\n}\nfunction add_binding_to_frame(symbol, value, frame) {\n set_head(frame, pair(symbol, head(frame)));\n set_tail(frame, pair(value, tail(frame)));\n}\n```\n\nWith sequential declaration processing, the scope of a\ndeclaration is no longer the entire block that immediately surrounds\nthe declaration, but rather just the portion of the block starting at\nthe point where the declaration occurs.", + "token_count": 287, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Internal Declarations", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Internal_Declarations_2" + }, + { + "content": "With sequential declaration processing, the scope of a\ndeclaration is no longer the entire block that immediately surrounds\nthe declaration, but rather just the portion of the block starting at\nthe point where the declaration occurs.\n\nAlthough we no longer have simultaneous scope, sequential\ndeclaration processing\nwill evaluate calls to the function\naccidental reason: Since the declarations\nof the internal functions come first, no calls to these functions\nwill be evaluated until all of them have been declared.\n\nHence,\nis_odd will have been declared by the time\nis_even is executed.\n\nIn fact,\nsequential declaration processing\nwill give the same result as our scanning-out-names evaluator in\nsection\nfor any function\nin which the\nt actually use any of\nthe declared names.\n\nExercise shows\nan example of a function that doesn t\nobey these restrictions, so that the alternative evaluator isn t\nequivalent to our scanning-out-names evaluator.\n\nSequential declaration processing is more efficient and easier to\nimplement than scanning out names.\n\nHowever, with sequential processing, the\ndeclaration to which a name refers may depend on the order in which\nthe statements in a block are evaluated.\n\nIn exercise , we see that\nviews may differ as to whether that is desirable.", + "token_count": 199, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Internal Declarations", + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_Internal_Declarations_3" + }, + { + "content": "Programmers write programs as text, i.e. sequences of characters, entered\nin a programming environment or a text editor.\n\nTo run our evaluator, we need\nto start with a representation of this program text as a JavaScript value.\n\nIn section we introduced strings to represent\ntext.\n\nWe would like to evaluate programs such as\n\"const size = 2; 5 * size;\"\nfrom section.\n\nUnfortunately, such program text does not provide enough structure to\nthe evaluator.\n\nIn this example, the program parts\n\"size = 2\" and\n\"5 * size\" look similar, but carry\nvery different meanings.\n\nAbstract syntax functions such as\ndeclaration_@value_@expression would be\ndifficult\nand error-prone to implement by examining the program text.\n\nIn this section, we therefore\nintroduce a function\nparse that translates program text\nto a tagged-list representation , reminiscent of\nthe tagged data of section.\n\nFor example, the application of\nparse to the program string\nabove produces a data structure that\nreflects the structure of the program: a sequence consisting\nof a constant declaration associating the name\nsize with the value 2\nand a multiplication.\n\n```javascript\nparse_declaration\n\nparse(\"const size = 2; 5 * size;\");\n```\n\nThe syntax functions used by the evaluator access the tagged-list representation produced by parse.\n\nThe evaluator is reminiscent of the.\n\nBoth programs operate on symbolic\ndata.\n\nIn both programs, the\nresult of operating on\nan object\nis determined by\noperating recursively on the pieces of the\nobject\nand combining\nthe results in a way that depends on the type of the\nobject.\n\nIn both programs we used\nthe objects\nare represented.\n\nIn\nthe differentiation program this meant that the same differentiation\nfunction\ncould deal with algebraic expressions in prefix form, in\ninfix form, or in some other form.\n\nFor the evaluator, this means that", + "token_count": 291, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Representing Components", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Representing_Components_1" + }, + { + "content": "For the evaluator, this means that\n\n```javascript\nthe syntax of the language being evaluated is determined\n\tsolely by parse and\n\tthe functions that classify and extract pieces of the\n\ttagged lists produced by parse.\n```\n\nSyntax abstraction in the evaluator.\n\nFigure depicts the\nparse.\n\nBelow we\ndescribe the parsing of program components and list the\ncorresponding syntax predicates and selectors, as well as\nconstructors if they are needed.\n\n```javascript\nheadline_4_1_2\n\n// functions from SICP JS 4.1.2\n```\n\nLiteral expressions\n\"literal\" and\nthe actual value.\n\\begin{Parsing}\n\\ll\\ \\mathit{literal}\\mhyphen\\mathit{expression}\\ \\gg & = &\n\\texttt{list(\"literal\", }\\mathit{value}\\texttt{)}\n\\end{Parsing}%\nwhere value is\nthe JavaScript value represented by the\nliteral-expression string.\n\nHere $\\ll\\ \\mathit{literal}\\mhyphen\\mathit{expression}\\ \\gg$ denotes the\nresult of parsing the string literal-expression.\n\n```javascript\nparse_literal_example\n\nparse(\"1;\");\n```\n\n```javascript\nparse_literal_example_2\n\nparse(\"'hello world';\");\n```\n\n```javascript\nparse_literal_example_3\n\nparse(\"null;\");\n```\n\nThe syntax predicate for literal expressions is is_literal.\n\n```javascript\nis_literal\n\t tagged_list\n\t is_literal_example\n\t true\n\nfunction is_literal(component) {\n return is_tagged_list(component, \"literal\");\n}\n\nfunction is_literal(component) {\n return is_tagged_list(component, \"literal\");\n}\nfunction literal_value(component) {\n return head(tail(component));\n}\n```\n\n```javascript\nis_literal_example\n\nconst my_program = parse(\"true; 1;\");\nconst my_true_statement = list_ref(list_ref(my_program, 1), 0);\nis_literal(my_true_statement);\n```\n\nIt is defined in terms of the function is_tagged_list , which identifies lists that begin with a designated string:\n\n```javascript\ntagged_list\n\t tagged_list_example\n\t true\n\nfunction is_tagged_list(component, the_tag) {\n return is_pair(component) && head(component) === the_tag;\n}\n```\n\n```javascript\ntagged_list_example\n\nis_tagged_list(list(\"name\", \"x\"), \"name\");\n```\n\nThe second element of the list that results from parsing a literal expression\nis its actual JavaScript value.\n\nThe selector for retrieving the value is\nliteral_value.\n\n```javascript\nliteral_value\n\t literal_value_example\n\t null\n\nfunction literal_value(component) {\n return head(tail(component));\n}\n```\n\n```javascript\nliteral_value_example\n\nliteral_value(parse(\"null;\"));\n```\n\nIn the rest of this section, we just list the syntax predicates and selectors, and omit their declarations if they just access the obvious list\n\nelements.\n\nWe provide a constructor for literals, which will come in handy:\n\n```javascript\nmake_literal\n\nfunction make_literal(value) {\n return list(\"literal\", value);\n}\n```", + "token_count": 308, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Representing Components", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Representing_Components_2" + }, + { + "content": "We provide a constructor for literals, which will come in handy:\n\nThe tagged-list representation for\n\"name\" as first\nelement and the string representing the name as second element.\n\\begin{Parsing}\n\\ll\\ \\mathit{name}\\ \\gg & = &\n\\texttt{list(\"name\", }\\mathit{symbol}\\texttt{)}\n\\end{Parsing}%\nwhere symbol is a string\nthat contains the characters that make up the\nname as written in the program.\n\nThe syntax predicate for names is\nis_name.\n\n```javascript\nvariable\n\t variable_example\n\t tagged_list\n\nfunction is_name(component) {\n return is_tagged_list(component, \"name\");\n}\n```\n\nThe symbol is accessed using the selector symbol_of_name.\n\n```javascript\nsymbol_of_name\n\t variable_example\n\t tagged_list\n\t variable\n\nfunction symbol_of_name(component) {\n return head(tail(component));\n}\n```\n\n```javascript\nvariable_example\n\nconst my_name_statement = parse(\"x;\");\ndisplay(is_name(my_name_statement));\ndisplay(symbol_of_name(my_name_statement));\n```\n\nWe provide a constructor for names, to be used by operator_combination_to_application :\n\n```javascript\nmake_name\n\t variable_example\n\t tagged_list\n\nfunction make_name(symbol) {\n return list(\"name\", symbol);\n}\n```\n\nWe do not need to distinguish between expressions and parse can ignore the difference between the two kinds of components: \\begin{ParsingNoPostPadding} \\ll\\ \\mathit{expression}\\texttt{;}\\ \\gg &\n\n= & \\ll\\ \\mathit{expression}\\ \\gg \\end{ParsingNoPostPadding}\n\nFunction applications\n\n```javascript\n$\\ll\\ $fun-expr(arg-expr$_1$, $\\ldots$, arg-expr$_n$)$\\ \\gg$ =\n list(\"application\",\n $\\ll\\ $fun-expr$\\ \\gg$,\n list($\\ll\\ $arg-expr$_1\\;\\gg$, $\\ldots$, $\\ll\\ $arg-expr$_n\\;\\gg$))\n```\n\nWe declare is_application as the syntax predicate and function_expression and arg_expressions as the selectors.\n\n```javascript\napplication\n tagged_list\n application_example\n\nfunction is_application(component) {\n return is_tagged_list(component, \"application\");\n}\nfunction function_expression(component) {\n return head(tail(component));\n}\nfunction arg_expressions(component) {\n return head(tail(tail(component)));\n}\n```\n\n```javascript\napplication_example\n\nconst my_application = parse(\"math_pow(3, 4);\");\ndisplay(is_application(my_application));\ndisplay(function_expression(my_application));\nconst my_expressions = arg_expressions(my_application);\ndisplay(no_arg_expressions(my_expressions));\ndisplay(first_arg_expression(my_expressions));\ndisplay(rest_arg_expressions(my_expressions));\n```\n\nWe add a constructor for function applications, to be used by operator_combination_to_application :\n\n```javascript\nmake_application\n variable\n variable_example\n tagged_list\n\nfunction make_application(function_expression, argument_expressions) {\n return list(\"application\",\n function_expression, argument_expressions);\n}\n```\n\nConditional expressions\n\n```javascript\n$\\ll\\ $predicate ? consequent-expression : alternative-expression$\\ \\gg$ =\n list(\"conditional_expression\",\n $\\ll\\ $predicate$\\ \\gg$,\n $\\ll\\ $consequent-expression$\\ \\gg$,\n $\\ll\\ $alternative-expression$\\ \\gg$)\n```\n\nSimilarly, conditional statements are parsed as follows:\n\n```javascript\n$\\ll\\ $if (predicate) consequent-block else alternative-block$\\ \\gg$ =\n list(\"conditional_statement\",\n $\\ll\\ $predicate$\\ \\gg$,\n $\\ll\\ $consequent-block$\\ \\gg$,\n $\\ll\\ $alternative-block$\\ \\gg$)\n```", + "token_count": 311, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Representing Components", + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_Representing_Components_3" + }, + { + "content": "Similarly, conditional statements are parsed as follows:\n\nThe syntax predicate is_conditional returns true for both kinds of conditionals, and the selectors conditional_predicate , conditional_consequent , and conditional_alternative can be applied to both\n\nkinds.\n\n```javascript\nif\n\t tagged_list\n\t if_example\n\t [ 'literal', [ 2, null ] ]\n\nfunction is_conditional(component) {\n return is_tagged_list(component, \"conditional_expression\") ||\n is_tagged_list(component, \"conditional_statement\");\n}\nfunction conditional_predicate(component) {\n return list_ref(component, 1);\n}\nfunction conditional_consequent(component) {\n return list_ref(component, 2);\n}\nfunction conditional_alternative(component) {\n return list_ref(component, 3);\n}\n```\n\n```javascript\nif_example\n\nconst my_cond_expr =\n parse(\"true ? 1 : 2;\");\ndisplay(is_conditional(my_cond_expr));\ndisplay(conditional_predicate(my_cond_expr));\ndisplay(conditional_consequent(my_cond_expr));\ndisplay(conditional_alternative(my_cond_expr));\n\nconst my_cond_expr =\n parse(\"true ? 1 : 2;\");\nis_conditional(my_cond_expr);\nconditional_predicate(my_cond_expr);\nconditional_consequent(my_cond_expr);\nconditional_alternative(my_cond_expr);\n```\n\nA lambda expression\n\n```javascript\n$\\ll\\ $(name$_1$, $\\ldots$, name$_n$) => expression$\\ \\gg$ =\n $\\ll\\ $(name$_1$, $\\ldots$, name$_n$) => { return expression; }$\\ \\gg$\n```\n\nA lambda expression whose body is a block is parsed as follows:\n\n```javascript\n$\\ll\\ $(name$_1$, $\\ldots$, name$_n$) => block$\\ \\gg$ =\n list(\"lambda_expression\",\n list($\\ll\\ $name$_1\\;\\gg$, $\\ldots$, $\\ll\\ $name$_n\\;\\gg$),\n $\\ll\\ $block$\\ \\gg$)\n```\n\nThe syntax predicate is\nis_lambda_expression\nand the selector for the body of the lambda expression is\nlambda_body.\n\nThe selector for the parameters, called\nlambda_parameter_symbols ,\nadditionally extracts the symbols from the names.\n\n```javascript\nlambda\n\t variable\n\t symbol_of_name\n\t tagged_list\n\t lambda_example\n\nfunction lambda_parameter_symbols(component) {\n return map(symbol_of_name, head(tail(component)));\n}\n\nfunction is_lambda_expression(component) {\n return is_tagged_list(component, \"lambda_expression\");\n}\nfunction lambda_parameter_symbols(component) {\n return map(symbol_of_name, head(tail(component)));\n}\nfunction lambda_body(component) {\n return head(tail(tail(component)));\n}\n```\n\n```javascript\nlambda_example\n\nconst my_lambda = parse(\"x => x\");\ndisplay(is_lambda_expression(my_lambda));\ndisplay(lambda_parameter_symbols(my_lambda));\ndisplay(lambda_body(my_lambda));\n```\n\nThe function function_decl_to_constant_decl needs a constructor for lambda expressions:\n\n```javascript\nmake_lambda_expression\n\t variable_example\n\t tagged_list\n\t variable\n\nfunction make_lambda_expression(parameters, body) {\n return list(\"lambda_expression\", parameters, body);\n}\n```\n\nA sequence statement sequence of statements is parsed as follows:\n\n```javascript\n$\\ll\\ $statement$_1$ $\\cdots$ statement$_n\\;\\gg$ =\n list(\"sequence\", list($\\ll\\ $statement$_1\\;\\gg$, $\\ldots$, $\\ll\\ $statement$_n\\;\\gg$))\n```\n\nThe syntax predicate is\nis_sequence and\nthe selector is sequence_statements.\n\nWe retrieve the first of a list of statements using\nfirst_statement and\nthe remaining statements using\nrest_statements.", + "token_count": 309, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Representing Components", + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_Representing_Components_4" + }, + { + "content": "We retrieve the first of a list of statements using\nfirst_statement and\nthe remaining statements using\nrest_statements.\n\nWe test\nwhether the list is empty using the predicate\nis_empty_sequence and\nwhether it contains only one element\nusing the predicate\nis_last_statement.\n\n```javascript\nbegin\n tagged_list\n begin_example\n [ 'literal', [ 45, null ] ]\n\nfunction first_statement(stmts) { return head(stmts); }\n\nfunction rest_statements(stmts) { return tail(stmts); }\n\nfunction is_empty_sequence(stmts) { return is_null(stmts); }\n\nfunction is_last_statement(stmts) { return is_null(tail(stmts)); }\n\nfunction is_sequence(stmt) {\n return is_tagged_list(stmt, \"sequence\");\n}\nfunction sequence_statements(stmt) {\n return head(tail(stmt));\n}\nfunction first_statement(stmts) {\n return head(stmts);\n}\nfunction rest_statements(stmts) {\n return tail(stmts);\n}\nfunction is_empty_sequence(stmts) {\n return is_null(stmts);\n}\nfunction is_last_statement(stmts) {\n return is_null(tail(stmts));\n}\n```\n\n```javascript\nbegin_example\n\nconst my_sequence = parse(\"1; true; 45;\");\ndisplay(is_sequence(my_sequence));\nconst my_actions = sequence_statements(my_sequence);\ndisplay(is_empty_sequence(my_actions));\ndisplay(is_last_statement(my_actions));\ndisplay(first_statement(my_actions));\ndisplay(rest_statements(my_actions));\n\nconst my_sequence = parse(\"1; true; 45;\");\nis_sequence(my_sequence);\nconst my_actions = sequence_statements(my_sequence);\nis_empty_sequence(my_actions);\nis_last_statement(my_actions);\nfirst_statement(my_actions);\nrest_statements(my_actions);\nlist_ref(rest_statements(my_actions), 1);\n```\n\nBlocks\n\\begin{Parsing}\n\\ll\\ \\texttt{\\{}\\ \\mathit{statements}\\ \\texttt{\\}}\\ \\gg\n& = &\n\\texttt{list(\"block\",}\\ \\ll\\ \\mathit{statements}\\ \\gg \\texttt{)}\n\\end{Parsing}%\nHere statements refers to a sequence of\nstatements, as shown above.\n\nThe syntax predicate is\nis_block\nand the selector is\nblock_body.\n\n```javascript\nblock\n\t tagged_list\n\t block_example\n\nfunction is_block(component) {\n return is_tagged_list(component, \"block\");\n}\nfunction block_body(component) {\n return head(tail(component));\n}\n\nfunction is_block(component) {\n return is_tagged_list(component, \"block\");\n}\nfunction block_body(component) {\n return head(tail(component));\n}\nfunction make_block(statement) {\n return list(\"block\", statement);\n}\n```\n\n```javascript\nblock_example\n\nconst my_block = parse(\"{ 1; true; 45; }\");\ndisplay(is_block(my_block));\ndisplay(block_body(my_block));\n```\n\nReturn statements \\begin{Parsing} \\ll\\ \\textbf{\\texttt{return}}\\ \\mathit{expression} \\texttt{;}\\ \\gg & = & \\texttt{list(\"return_statement\",}\\ \\ll\\ \\mathit{expression}\\ \\gg \\texttt{)} \\end{Parsing}% The syntax predicate and selector are, respectively, is_return_statement\n\nand return_expression.\n\n```javascript\nreturn\n\t tagged_list\n\t return_example\n\t [ 'name', [ 'x', null ] ]\n\nfunction is_return_statement(component) {\n return is_tagged_list(component, \"return_statement\");\n}\nfunction return_expression(component) {\n return head(tail(component));\n}\n```\n\n```javascript\nreturn_example\n\nconst my_function_declaration = parse(\"function f(x) { return x; }\");\nconst my_return = list_ref(my_function_declaration, 3);\nlist_ref(my_return, 1);\n```", + "token_count": 306, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Representing Components", + "chunk_index": 5, + "chunk_id": "Metalinguistic_Abstraction_Representing_Components_5" + }, + { + "content": "and return_expression.\n\nAssignments\n\\begin{Parsing}\n\\ll\\;\\mathit{name} \\ \\texttt{=}\\ \\mathit{expression}\\;\\gg & = &\n\\texttt{list(\"assignment\",}\\ \\ll\\;\\mathit{name}\\;\\gg \\texttt{, }\\ll\\;\\mathit{expression}\\;\\gg \\texttt{)}\n\\end{Parsing}%\nThe syntax predicate is\nis_assignment\nand the selectors are\nassignment_symbol\nand\nassignment_value_expression.\n\nThe symbol is wrapped in a tagged list representing the name, and thus\nassignment_symbol needs to\nunwrap it.\n\n```javascript\nassignment\n tagged_list\n assignment_example\n [ 'literal', [ 1, null ] ]\n\nfunction assignment_symbol(component) {\n return symbol_of_name(head(tail(component))));\n}\n\nfunction is_assignment(component) {\n return is_tagged_list(component, \"assignment\");\n}\nfunction assignment_symbol(component) {\n return head(tail(head(tail(component))));\n}\nfunction assignment_value_expression(component) {\n return head(tail(tail(component)));\n}\n```\n\n```javascript\nassignment_example\n\nconst my_assignment_statement = parse(\"x = 1;\");\ndisplay(assignment_symbol(my_assignment_statement));\ndisplay(assignment_value_expression(my_assignment_statement));\n\nconst my_assignment_statement = parse(\"x = 1;\");\nassignment_symbol(my_assignment_statement);\nassignment_value_expression(my_assignment_statement);\n```\n\nConstant and variable declarations\n\n```javascript\n$\\ll\\ $const name = expression;$\\ \\gg$ =\n list(\"constant_declaration\", $\\ll\\ $name$\\ \\gg$, $\\ll\\ $expression$\\ \\gg$)\n\n$\\ll\\ $let name = expression;$\\ \\gg$ =\n list(\"variable_declaration\", $\\ll\\ $name$\\ \\gg$, $\\ll\\ $expression$\\ \\gg$)\n```\n\nThe selectors declaration_symbol and declaration_value_expression apply to both kinds.\n\n```javascript\ndeclaration_symbol\n\t tagged_list\n\t definition_example\n\nfunction declaration_symbol(component) {\n return symbol_of_name(head(tail(component)));\n}\nfunction declaration_value_expression(component) {\n return head(tail(tail(component)));\n}\n```\n\nThe function function_decl_to_constant_decl needs a constructor for constant declarations:\n\n```javascript\nmake_constant_declaration\n\t tagged_list\n\t definition\n\t definition_example\n\nfunction make_constant_declaration(name, value_expression) {\n return list(\"constant_declaration\", name, value_expression);\n}\n```\n\nFunction declarations\n\n```javascript\n$\\ll\\ $function name(name$_1$, $\\ldots$ name$_n$) block$\\ \\gg$ =\n list(\"function_declaration\",\n $\\ll\\ $name$\\ \\gg$,\n list($\\ll\\ $name$_1\\;\\gg$, $\\ldots$, $\\ll\\ $name$_n\\;\\gg$),\n $\\ll\\ $block$\\ \\gg$)\n```\n\nThe syntax predicate\nis_function_declaration\nrecognizes these.\n\nThe selectors are\nfunction_declaration_name ,\nfunction_declaration_parameters , and\nfunction_declaration_body.\n\n```javascript\nfunction_declaration_syntax\n tagged_list\n\nfunction is_function_declaration(component) {\n return is_tagged_list(component, \"function_declaration\");\n}\nfunction function_declaration_name(component) {\n return list_ref(component, 1);\n}\nfunction function_declaration_parameters(component) {\n return list_ref(component, 2);\n}\nfunction function_declaration_body(component) {\n return list_ref(component, 3);\n}\n```\n\nThe syntax predicate is_declaration returns true for all three kinds of declarations.\n\n```javascript\ndefinition\n\t tagged_list\n\t definition_example\n\nfunction is_declaration(component) {\n return is_tagged_list(component, \"constant_declaration\") ||\n is_tagged_list(component, \"variable_declaration\") ||\n is_tagged_list(component, \"function_declaration\");\n}\n```\n\n```javascript\ndefinition_example\n\nconst my_declaration_statement = parse(\"let x = 1;\");\ndisplay(is_declaration(my_declaration_statement));\ndisplay(declaration_symbol(my_declaration_statement));\ndisplay(declaration_value_expression(my_declaration_statement));\n```\n\nSome evaluate transforms into a constant declaration whose value expression is a lambda expression.", + "token_count": 315, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Representing Components", + "chunk_index": 6, + "chunk_id": "Metalinguistic_Abstraction_Representing_Components_6" + }, + { + "content": "Some evaluate transforms into a constant declaration whose value expression is a lambda expression.\n\n```javascript\nfunction_declaration\n\t tagged_list\n\nfunction function_decl_to_constant_decl(component) {\n return make_constant_declaration(\n function_declaration_name(component),\n make_lambda_expression(\n function_declaration_parameters(component),\n function_declaration_body(component)));\n}\n\nfunction is_function_declaration(component) {\n return is_tagged_list(component, \"function_declaration\");\n}\nfunction function_declaration_name(component) {\n return list_ref(component, 1);\n}\nfunction function_declaration_parameters(component) {\n return list_ref(component, 2);\n}\nfunction function_declaration_body(component) {\n return list_ref(component, 3);\n}\nfunction function_decl_to_constant_decl(component) {\n return make_constant_declaration(\n function_declaration_name(component),\n make_lambda_expression(\n function_declaration_parameters(component),\n function_declaration_body(component)));\n}\n```\n\nImplementing the evaluation of function declarations in this way simplifies the evaluator because it reduces the number of syntactic forms for which the evaluation process\n\nmust be explicitly specified.\n\nSimilarly, we define\n\n```javascript\n$\\ll\\ $unary-operator expression$\\ \\gg$ =\n list(\"unary_operator_combination\",\n \"unary-operator\",\n list($\\ll\\ $expression$\\ \\gg$))\n```\n\nwhere unary-operator is\n!\n\n(for logical negation) or\n-unary (for numeric negation), and\n\n```javascript\n$\\ll\\ $expression$_1$ binary-operator expression$_2\\;\\gg$ =\n list(\"binary_operator_combination\",\n \"binary-operator\",\n list($\\ll\\ $expression$_1\\;\\gg$, $\\ll\\ $expression$_2\\;\\gg$))\n```\n\nwhere binary-operator is\n+ ,\n- ,\n* ,\n/ ,\n% ,\n=== ,\n!== ,\n> ,\n< ,\n>= or\n<=.\n\nThe syntax predicates are\nis_operator_combination ,\nis_unary_operator_combination , and\nis_binary_operator_combination ,\nand the selectors are\noperator_symbol ,\nfirst_operand , and\nsecond_operand.\n\n```javascript\noperator_combination\n\nfunction is_operator_combination(component) {\n return is_unary_operator_combination(component) ||\n is_binary_operator_combination(component);\n}\nfunction is_unary_operator_combination(component) {\n return is_tagged_list(component, \"unary_operator_combination\");\n}\nfunction is_binary_operator_combination(component) {\n return is_tagged_list(component, \"binary_operator_combination\");\n}\nfunction operator_symbol(component) {\n return list_ref(component, 1);\n}\nfunction first_operand(component) {\n return list_ref(component, 2);\n}\nfunction second_operand(component) {\n return list_ref(component, 3);\n}\n```\n\nThe evaluator uses operator_combination_to_application to transform an\n\n```javascript\noperator_combination_to_application\n\t operator_combination\n\t make_application\n\nfunction operator_combination_to_application(component) {\n const operator = operator_symbol(component);\n return is_unary_operator_combination(component)\n ? make_application(make_name(operator),\n list(first_operand(component)))\n : make_application(make_name(operator),\n list(first_operand(component),\n second_operand(component)));\n}\n```\n\nComponents (such as function declarations and operator combinations) that we\nchoose\nto implement as syntactic transformations are called\nderived components.\n\nLogical composition operations are also\nderived components (see exercise ).\n\nFor example, recall the imperative-style version of the iterative factorial function from section :", + "token_count": 300, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Representing Components", + "chunk_index": 7, + "chunk_id": "Metalinguistic_Abstraction_Representing_Components_7" + }, + { + "content": "For example, recall the imperative-style version of the iterative factorial function from section :\n\n```javascript\nfactorial_imperative_2\n\t factorial_example\n\t 120\n\nfunction factorial(n) {\n let product = 1;\n let counter = 1;\n function iter() {\n if (counter > n) {\n return product;\n } else {\n product = counter * product;\n counter = counter + 1;\n return iter();\n }\n }\n return iter();\n}\n```\n\n\\newpage\\noindent We can formulate the same algorithm using a while loop as follows:\n\n```javascript\nfactorial_with_loop\n\nfunction factorial(n) {\n let product = 1;\n let counter = 1;\n while (counter <= n) {\n product = counter * product;\n counter = counter + 1;\n }\n return product;\n}\n```\n\nWhile loops are parsed as follows:\n\n```javascript\n$\\ll\\ $while (predicate) block$\\ \\gg$ =\n list(\"while_loop\", $\\ll\\ $predicate$\\ \\gg$, $\\ll\\ $block$\\ \\gg$)\n```\n\nFor such a program, JavaScript\nstatically\ndistinguishes between value-producing and\nnon-value-producing statements.\n\n(Here\nstatically means that\nwe can make the distinction by inspecting the program\nrather than by running it.)\nAll declarations are\nnon-value-producing, and all\nStatements and conditional statements are\nvalue-producing.\n\nThe value of an statement is the value of the expression.\n\nThe value of a conditional statement is the value of the branch that\ngets executed, or the value\nundefined if that branch is\nnot value-producing.\n\nA block is value-producing if its body (sequence of statements)\nis value-producing, and then its value is the value of its body.\n\nA sequence is value-producing if any of\nits component statements is value-producing, and then its value is\nthe value of its last value-producing component statement.\n\nFinally, if the whole\nprogram is not value-producing, its value is the value\nundefined.\n-\n-\nAccording to this specification, what are the values of the\nfollowing four programs?", + "token_count": 281, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Representing Components", + "chunk_index": 8, + "chunk_id": "Metalinguistic_Abstraction_Representing_Components_8" + }, + { + "content": "Finally, if the whole\nprogram is not value-producing, its value is the value\nundefined.\n-\n-\nAccording to this specification, what are the values of the\nfollowing four programs?\n\n```javascript\n1; 2; 3;\n\n1; { if (true) {} else { 2; } }\n\n1; const x = 2;\n\n1; { let x = 2; { x = x + 3; } }\n```\n\n- - Modify the evaluator to adhere to this specification.", + "token_count": 73, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Representing Components", + "chunk_index": 9, + "chunk_id": "Metalinguistic_Abstraction_Representing_Components_9" + }, + { + "content": "Given the evaluator, we have in our hands a description\n(expressed in JavaScript)\nof the process by which\nJavaScript statements and expressions\nare evaluated.\n\nOne advantage of expressing the evaluator as a program is\nthat we can run the program.\n\nThis gives us, running within\nJavaScript,\na working model of how\nJavaScript\nitself evaluates expressions.\n\nThis can serve as a framework for\nexperimenting with evaluation rules, as we shall do later in this chapter.\n\nOur evaluator program reduces expressions ultimately to the application of\nprimitive\nfunctions.\n\nTherefore, all that we need to run the evaluator is to create a mechanism\nthat calls on the underlying\nJavaScript\nsystem to model the application of primitive\nfunctions.\n\nThere must be a binding for each primitive function name and operator, so that when evaluate evaluates the function expression of an application of a\n\nprimitive, it will find an object to pass to functions and operators that can appear in the expressions we will be evaluating.\n\n```javascript\nThe global environment also includes bindings for\n\tundefined\n\tand other names,\n```\n\nso that they can be used as constants in expressions to be evaluated.\n\n```javascript\nheadline_4_1_4\n\n// functions from SICP JS 4.1.4\n```\n\n```javascript\nsetup_environment\n setup_environment_example\n tagged_list\n extend_environment\n enclosing_environment\n make_frame\n primitive_constants\n primitive_procedures\n primitive_procedure\n '/'\n\nfunction setup_environment() {\n return extend_environment(append(primitive_function_symbols,\n primitive_constant_symbols),\n append(primitive_function_objects,\n primitive_constant_values),\n the_empty_environment);\n}\n```\n\n```javascript\nsetup_environment_example\n\nconst the_global_environment = setup_environment();\n\nthe_global_environment;\n\nconst the_global_environment = setup_environment();\n\nlist_ref(head(head(the_global_environment)), 12);\n```\n\n```javascript\nthe_global_environment\n setup_environment\n the_global_environment_example\n\nconst the_global_environment = setup_environment();\n```\n\n```javascript\nthe_global_environment_example\n\nthe_global_environment;\n```\n\nIt does not matter how we represent primitive function objects, so long\nas is_primitive_function\nand apply_primitive_function.\n\nWe\nhave chosen to represent a primitive function as a list beginning with\nthe string \"primitive\" and\ncontaining a function in the underlying JavaScript that implements that\nprimitive.\n\n```javascript\nprimitive_procedure\n\t tagged_list\n\t primitive_procedure_example\n\t true\n\nfunction is_primitive_function(fun) {\n return is_tagged_list(fun, \"primitive\");\n}\n\nfunction primitive_implementation(fun) { return head(tail(fun)); }\n```", + "token_count": 308, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Running the Evaluator as a Program", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Running_the_Evaluator_as_a_Program_1" + }, + { + "content": "We\nhave chosen to represent a primitive function as a list beginning with\nthe string \"primitive\" and\ncontaining a function in the underlying JavaScript that implements that\nprimitive.\n\n```javascript\nprimitive_procedure_example\n\nconst my_primitive_plus =\n list(\"primitive\", (x, y) => x + y );\ndisplay(is_primitive_function(my_primitive_plus));\ndisplay(primitive_implementation(my_primitive_plus));\n\nconst my_primitive_plus =\n list(\"primitive\", (x, y) => x + y );\nprimitive_implementation(my_primitive_plus);\nis_primitive_function(my_primitive_plus);\n```\n\n```javascript\nThe function\n setup_environment\n```\n\nwill get the primitive names and implementation functions from a list:\n\n```javascript\nprimitive_procedures\n primitive_procedures_example\n 20\n\nconst primitive_functions = list(list(\"head\", head ),\n list(\"tail\", tail ),\n list(\"pair\", pair ),\n list(\"is_null\", is_null ),\n list(\"+\", (x, y) => x + y ),\n more primitive functions\n );\n\nconst primitive_function_symbols =\n map(f => head(f), primitive_functions);\n\nconst primitive_function_objects =\n map(f => list(\"primitive\", head(tail(f))),\n primitive_functions);\n\nconst primitive_functions = list(\n list(\"head\", head ),\n list(\"tail\", tail ),\n list(\"pair\", pair ),\n list(\"list\", list ),\n list(\"is_null\", is_null ),\n list(\"display\", display ),\n list(\"error\", error ),\n list(\"math_abs\",math_abs ),\n list(\"+\", (x, y) => x + y ),\n list(\"-\", (x, y) => x - y ),\n list(\"-unary\", x => - x ),\n list(\"*\", (x, y) => x * y ),\n list(\"/\", (x, y) => x / y ),\n list(\"%\", (x, y) => x % y ),\n list(\"===\", (x, y) => x === y),\n list(\"!==\", (x, y) => x !== y),\n list(\"<\", (x, y) => x < y),\n list(\"<=\", (x, y) => x <= y),\n list(\">\", (x, y) => x > y),\n list(\">=\", (x, y) => x >= y),\n list(\"!\", x => ! x)\n );\nconst primitive_function_symbols =\n map(head, primitive_functions);\nconst primitive_function_objects =\n map(fun => list(\"primitive\", head(tail(fun))),\n primitive_functions);\n```\n\n```javascript\nprimitive_procedures_example\n\nprimitive_functions;\n\nlength(primitive_functions);\n```\n\nSimilar to primitive functions, we define other primitive constants that are installed in the global environment by the function setup_environment.", + "token_count": 279, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Running the Evaluator as a Program", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Running_the_Evaluator_as_a_Program_2" + }, + { + "content": "Similar to primitive functions, we define other primitive constants that are installed in the global environment by the function setup_environment.\n\n```javascript\nprimitive_constants\n\t primitive_constants_example\n\t 5\n\nconst primitive_constants = list(list(\"undefined\", undefined),\n list(\"math_PI\", math_PI)\n more primitive constants\n );\n\nconst primitive_constant_symbols =\n map(c => head(c), primitive_constants);\n\nconst primitive_constant_values =\n map(c => head(tail(c)), primitive_constants);\n\nconst primitive_constants = list(list(\"undefined\", undefined),\n list(\"Infinity\", Infinity),\n list(\"math_PI\", math_PI),\n list(\"math_E\", math_E),\n list(\"NaN\", NaN)\n );\nconst primitive_constant_symbols =\n map(c => head(c), primitive_constants);\nconst primitive_constant_values =\n map(c => head(tail(c)), primitive_constants);\n```\n\n```javascript\nprimitive_constants_example\n\nprimitive_constants;\n\nlength(primitive_constants);\n```\n\nTo apply a primitive function, we simply apply the implementation function to the arguments, using the underlying JavaScript system:\n\n```javascript\napply_primitive_procedure\n apply_primitive_procedure_example\n 3\n\nfunction apply_primitive_function(fun, arglist) {\n return apply_in_underlying_javascript(\n primitive_implementation(fun), arglist);\n}\n```\n\n```javascript\napply_primitive_procedure_example\n primitive_procedure\n primitive_procedure_example\n\napply_primitive_function(my_primitive_plus, list(1, 2));\n```", + "token_count": 124, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Running the Evaluator as a Program", + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_Running_the_Evaluator_as_a_Program_3" + }, + { + "content": "To apply a primitive function, we simply apply the implementation function to the arguments, using the underlying JavaScript system:\n\n```javascript\nFor convenience in running the metacircular evaluator, we provide a\n\tdriver loop that models the read-evaluate-print loop of\n\tthe underlying JavaScript system. It prints a\n\tprompt and reads an input program as a string.\n\tIt transforms the program string\n\tinto a tagged-list representation of the statement as described in\n\tsectiona\n\tprocess called parsing and accomplished by the primitive function\n\tparse.\n\tWe precede each printed result by\n\tan output prompt so as to distinguish the value of the\n\tprogram from other output that may be printed. The driver loop gets\n\tthe program environment of the previous program as argument.\n\tAs described at the end of section, the\n\tdriver loop treats the program as if it were in a block: It\n\tscans out the declarations, extends the given environment by a frame\n\tcontaining a binding of each name to\n\t\"*unassigned*\", and evaluates\n\tthe program with respect to the extended environment, which\n\tis then passed as argument to the next iteration of the driver loop.\n\n\t driver_loop\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\t user_print\n\t user_read\n\t driver_loop_example\n\nconst input_prompt = \"M-evaluate input: \";\nconst output_prompt = \"M-evaluate value: \";\n\nfunction driver_loop(env) {\n const input = user_read(input_prompt);\n if (is_null(input)) {\n display(\"evaluator terminated\");\n } else {\n const program = parse(input);\n const locals = scan_out_declarations(program);\n const unassigneds = list_of_unassigned(locals);\n const program_env = extend_environment(locals, unassigneds, env);\n const output = evaluate(program, program_env);\n user_print(output_prompt, output);\n return driver_loop(program_env);\n }\n}\n\nconst input_prompt = \"\\nM-evaluate input:\\n\";\nconst output_prompt = \"\\nM-evaluate value:\\n\";\n\nfunction driver_loop(env, history) {\n const input = user_read(history);\n if (is_null(input)) {\n display(\"\", history + \"\\n--- session end ---\\n\");\n } else {\n const program = parse(input);\n const locals = scan_out_declarations(program);\n const unassigneds = list_of_unassigned(locals);\n const program_env = extend_environment(\n locals, unassigneds, env);\n const output = evaluate(program, program_env);\n const new_history = history +\n input_prompt +\n input +\n output_prompt +\n to_string(output);\n return driver_loop(program_env, new_history);\n }\n}\n\n\"metacircular evaluator loaded\";\n```", + "token_count": 322, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Running the Evaluator as a Program", + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_Running_the_Evaluator_as_a_Program_4" + }, + { + "content": "To apply a primitive function, we simply apply the implementation function to the arguments, using the underlying JavaScript system:\n\n```javascript\nWe use JavaScript's prompt function\n\tto request and read the input string from the user:\n\n\t user_read\n\nfunction user_read(prompt_string) {\n return prompt(prompt_string);\n}\n```\n\nThe function\nprompt returns\nnull when the user cancels the\ninput.\n\nWe use a special printing\nfunction user_print,\nto avoid printing the environment part of a compound\nfunction,\nwhich may be a very long list (or may even contain cycles).\n\n```javascript\nuser_print\n user_print_example\n\nfunction user_print(string, object) {\n function prepare(object) {\n return is_compound_function(object)\n ? \"< compound-function >\"\n : is_primitive_function(object)\n ? \"< primitive-function >\"\n : is_pair(object)\n ? pair(prepare(head(object)),\n prepare(tail(object)))\n : object;\n }\n display(string + \" \" + stringify(prepare(object)));\n}\n\nfunction to_string(object) {\n return is_compound_function(object)\n ? \"\"\n : is_primitive_function(object)\n ? \"\"\n : is_pair(object)\n ? \"[\" + to_string(head(object)) + \", \"\n + to_string(tail(object)) + \"]\"\n : stringify(object);\n}\n\nfunction user_print(prompt_string, object) {\n display(\"----------------------------\",\n prompt_string + \"\\n\" + to_string(object) + \"\\n\");\n}\n```\n\n```javascript\nuser_print_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nuser_print(\"output: \",\n evaluate(parse(\"1 + 2;\"),\n the_global_environment));\n```\n\nNow all we need to do to run the evaluator is to initialize the global\nenvironment and start the driver loop.\n\nHere is a sample interaction:\n\n```javascript\ndriver_loop_example\n driver_loop\n\nconst the_global_environment = setup_environment();\ndriver_loop(the_global_environment);\n\ndriver_loop(the_global_environment, \"--- session start ---\");\n```\n\n```javascript\nmeta_append\n driver_loop\n driver_loop_example\n\nM-evaluate input:\n\nfunction append(xs, ys) {\n return is_null(xs)\n ? ys\n : pair(head(xs), append(tail(xs), ys));\n}\n\n// press \"Run\" to start the driver loop\n// M-evaluate input:\n// function append(xs, ys) { return is_null(xs) ? ys : pair(head(xs), append(tail(xs), ys)); }\n// M-evaluate value:\n// undefined\n\n// M-evaluate input:\n// append(list(\"a\", \"b\", \"c\"), list(\"d\", \"e\", \"f\"));\n// M-evaluate value:\n// [\"a\", [\"b\", [\"c\", [\"d\", [\"e\", [\"f\", null]]]]]]\n```", + "token_count": 285, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Running the Evaluator as a Program", + "chunk_index": 5, + "chunk_id": "Metalinguistic_Abstraction_Running_the_Evaluator_as_a_Program_5" + }, + { + "content": "Here is a sample interaction:\n\n```javascript\nmeta_append_example\n driver_loop\n driver_loop_example\n\nM-evaluate input:\n\nappend(list(\"a\", \"b\", \"c\"), list(\"d\", \"e\", \"f\"));\n\n// press \"Run\" to start the driver loop\n// M-evaluate input:\n// function append(xs, ys) { return is_null(xs) ? ys : pair(head(xs), append(tail(xs), ys)); }\n// M-evaluate value:\n// undefined\n\n// M-evaluate input:\n// append(list(\"a\", \"b\", \"c\"), list(\"d\", \"e\", \"f\"));\n// M-evaluate value:\n// [\"a\", [\"b\", [\"c\", [\"d\", [\"e\", [\"f\", null]]]]]]\n```", + "token_count": 69, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Running the Evaluator as a Program", + "chunk_index": 6, + "chunk_id": "Metalinguistic_Abstraction_Running_the_Evaluator_as_a_Program_6" + }, + { + "content": "The evaluator implemented above is simple, but it is very\ncomponents\nis interleaved\nwith their execution.\n\nThus if a program is executed many times, its\nsyntax is analyzed many times.\n\nConsider, for example, evaluating\nfactorial(4)\nusing the following definition of\n\n```javascript\nfactorial_4_1_7\n factorial_example\n 120\n\nfunction factorial(n) {\n return n === 1\n ? 1\n : factorial(n - 1) * n;\n}\n```\n\nEach time\na conditional\nexpression and extract the predicate.\n\nOnly then can it evaluate the\npredicate and dispatch on its value.\n\nEach time it evaluates the expression\nfactorial(n - 1) * n,\nor the subexpressions\nfactorial(n - 1)\nand\nn - 1,\nthe evaluator must perform the case analysis in\nevaluate\nto determine that the expression is an application, and must extract\nits function expression and argument expressions.\n\nThis analysis is expensive.\n\nPerforming it repeatedly is wasteful.\n\nWe can transform the evaluator to be significantly more efficient by\narranging things so that syntactic analysis is performed only\nonce.\nevaluate,\nwhich takes\na component\nand an environment, into two parts.\n\nThe\nfunction\ncomponent.\n\nIt performs the syntactic\nanalysis and returns a new\nfunction , the\nexecution\nfunction , that\nencapsulates the work to be done in executing the analyzed\ncomponent.\n\nThe execution\nfunction\ntakes an environment as its\nargument and completes the evaluation.\n\nThis saves work because\na component,\nwhile the execution\nfunction\nmay be called many times.\n\nWith the separation into analysis and execution, evaluate now becomes\n\n```javascript\nanalyze_headline\n\n// functions from SICP JS 4.1.7\n```\n\n```javascript\nevaluate_4_1_7\n analyze\n evaluate_4_1_7_simple_function\n\nfunction evaluate(component, env) {\n return analyze(component)(env);\n}\n```\n\n```javascript\nevaluate_4_1_7_simple_function\n evaluate_4_1_7\n 5\n\nevaluate(parse(\"{ function f(x) { return x + 1; } f(4); }\"),\n the_global_environment);\n```\n\nThe result of calling\nfunction\nto be applied to the environment.", + "token_count": 287, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Separating Syntactic Analysis from Execution", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Separating_Syntactic_Analysis_from_Execution_1" + }, + { + "content": "The result of calling\nfunction\nto be applied to the environment.\n\nThe\nfunction\nis the same case analysis as performed by the original\nevaluate\nof section , except that the\nfunctions\nto which we dispatch perform only analysis, not full evaluation:\n\n```javascript\nanalyze_example\n\nanalyze(parse(\"{ const x = 1; x + 1; }\"))\n (the_global_environment);\n```\n\n```javascript\nanalyze\n headline_4_1_1\n list_of_unassigned\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n analyze_headline\n analyze_literal\n analyze_variable\n analyze_assignment\n analyze_if\n scan_out_declarations\n analyze_lambda\n analyze_sequence\n analyze_block\n analyze_return_statement\n analyze_application\n analyze_example\n 2\n\nfunction analyze(component) {\n return is_literal(component)\n ? analyze_literal(component)\n : is_name(component)\n ? analyze_name(component)\n : is_application(component)\n ? analyze_application(component)\n : is_operator_combination(component)\n ? analyze(operator_combination_to_application(component))\n : is_conditional(component)\n ? analyze_conditional(component)\n : is_lambda_expression(component)\n ? analyze_lambda_expression(component)\n : is_sequence(component)\n ? analyze_sequence(sequence_statements(component))\n : is_block(component)\n ? analyze_block(component)\n : is_return_statement(component)\n ? analyze_return_statement(component)\n : is_function_declaration(component)\n ? analyze(function_decl_to_constant_decl(component))\n : is_declaration(component)\n ? analyze_declaration(component)\n : is_assignment(component)\n ? analyze_assignment(component)\n : error(component, \"unknown syntax -- analyze\");\n}\n```\n\nHere is the simplest syntactic analysis\n\n```javascript\nfunction,\n\twhich handles literal expressions.\n```\n\nIt returns an execution function that ignores its environment argument and just returns the value of the literal:\n\n```javascript\nanalyze_literal_example\n\n// null is the empty environment (not used here)\nanalyze_literal(parse(\"true;\"))(null);\n```\n\n```javascript\nanalyze_literal\n functions_4_1_2\n analyze_literal_example\n true\n\nfunction analyze_literal(component) {\n return env => literal_value(component);\n}\n```\n\nLooking up the value of a name must still be done in the execution phase, since this depends upon knowing the environment.\n\n```javascript\nanalyze_variable_example\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nanalyze_name(parse(\"myname;\"))\n (extend_environment(list(\"myname\"), list(1),\n the_global_environment));\n```\n\n```javascript\nanalyze_variable\n analyze_variable_example\n 1\n\nfunction analyze_name(component) {\n return env => lookup_symbol_value(symbol_of_name(component), env);\n}\n```\n\nFor conditionals, we extract and analyze the predicate, consequent, and alternative at analysis time.\n\n```javascript\nanalyze_if_example\n\t analyze\n\nanalyze_conditional(parse(\"true ? 3 : 7;\"))\n (the_global_environment);\n```\n\n```javascript\nanalyze_if\n\t analyze_if_example\n\t 3\n\nfunction analyze_conditional(component) {\n const pfun = analyze(conditional_predicate(component));\n const cfun = analyze(conditional_consequent(component));\n const afun = analyze(conditional_alternative(component));\n return env => is_truthy(pfun(env)) ? cfun(env) : afun(env);\n}\n```\n\nAnalyzing a lambda expression also achieves a major gain in efficiency: We analyze the lambda body only once, even though functions resulting from evaluation of", + "token_count": 315, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Separating Syntactic Analysis from Execution", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Separating_Syntactic_Analysis_from_Execution_2" + }, + { + "content": "Analyzing a lambda expression also achieves a major gain in efficiency: We analyze the lambda body only once, even though functions resulting from evaluation of\n\nthe lambda expression may be applied many times.\n\n```javascript\nanalyze_lambda_example\n\t analyze\n\nlist_ref(analyze_lambda_expression(parse(\"x => x;\"))\n (the_global_environment),\n 2)\n (extend_environment(list(\"x\"), list(7),\n the_global_environment));\n```\n\n```javascript\nanalyze_lambda\n\t analyze_lambda_example\n\t [ 'return_value', [ 7, null ] ]\n\nfunction analyze_lambda_expression(component) {\n const params = lambda_parameter_symbols(component);\n const bfun = analyze(lambda_body(component));\n return env => make_function(params, bfun, env);\n}\n```\n\nAnalysis of a sequence of statements is more involved.\n\n```javascript\nanalyze_sequence_example\n\t analyze\n\nanalyze_sequence(sequence_statements(parse(\"10; 20; 30;\")))\n (the_global_environment);\n```\n\n```javascript\nanalyze_sequence\n\t analyze_sequence_example\n\t 30\n\nfunction analyze_sequence(stmts) {\n function sequentially(fun1, fun2) {\n return env => {\n const fun1_val = fun1(env);\n return is_return_value(fun1_val)\n ? fun1_val\n : fun2(env);\n };\n }\n function loop(first_fun, rest_funs) {\n return is_null(rest_funs)\n ? first_fun\n : loop(sequentially(first_fun, head(rest_funs)),\n tail(rest_funs));\n }\n const funs = map(analyze, stmts);\n return is_null(funs)\n ? env => undefined\n : loop(head(funs), tail(funs));\n}\n```\n\nThe body of a\nblock is scanned only once for local declarations.\n\nThe bindings are installed in the environment when\nthe execution function for the block is called.\n\n```javascript\nanalyze_block_example\n\t analyze\n\nanalyze_block(parse(\"{ const x = 4; x; }\"))\n (the_global_environment);\n```\n\n```javascript\nanalyze_block\n\t list_of_unassigned\n\t analyze_block_example\n\t 4\n\nfunction analyze_block(component) {\n const body = block_body(component);\n const bfun = analyze(body);\n const locals = scan_out_declarations(body);\n const unassigneds = list_of_unassigned(locals);\n return env => bfun(extend_environment(locals, unassigneds, env));\n}\n```\n\nFor return statements, we analyze the return expression.\n\nThe execution function for the return statement simply calls\nthe execution function for the return expression and wraps\nthe result in a return value.\n\n```javascript\nanalyze_return_statement_example\n\t analyze\n\nanalyze_return_statement(list_ref(parse(\"() => x + 1;\"), 2))\n (extend_environment(list(\"x\"), list(6), the_global_environment));\n```\n\n```javascript\nanalyze_return_statement\n\t analyze_return_statement_example\n\t [ 'return_value', [ 7, null ] ]\n\nfunction analyze_return_statement(component) {\n const rfun = analyze(return_expression(component));\n return env => make_return_value(rfun(env));\n}\n```\n\n```javascript\nThe function\n analyze_assignment\n```\n\nmust defer actually setting the variable until the execution, when the\nenvironment has been supplied.", + "token_count": 307, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Separating Syntactic Analysis from Execution", + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_Separating_Syntactic_Analysis_from_Execution_3" + }, + { + "content": "must defer actually setting the variable until the execution, when the\nenvironment has been supplied.\n\nHowever, the fact that the\nassignment-value expression\ncan be analyzed (recursively) during analysis is a major gain in\nefficiency, because the\nassignment-value expression\nwill now be analyzed only once.\n\nThe same holds true for\nconstant and variable declarations.\n\n```javascript\nanalyze_assignment_example\n analyze\n\nanalyze_assignment(parse(\"x = x + 1;\"))\n (extend_environment(list(\"x\"), list(7), the_global_environment));\n```\n\n```javascript\nanalyze_assignment\n analyze_assignment_example\n 8\n\nfunction analyze_assignment(component) {\n const symbol = assignment_symbol(component);\n const vfun = analyze(assignment_value_expression(component));\n return env => {\n const value = vfun(env);\n assign_symbol_value(symbol, value, env);\n return value;\n };\n}\nfunction analyze_declaration(component) {\n const symbol = declaration_symbol(component);\n const vfun = analyze(declaration_value_expression(component));\n return env => {\n assign_symbol_value(symbol, vfun(env), env);\n return undefined;\n };\n}\n```\n\nOur new evaluator uses the same data structures, syntax functions, and runtime support functions as in sections , , and.", + "token_count": 138, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "Separating Syntactic Analysis from Execution", + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_Separating_Syntactic_Analysis_from_Execution_4" + }, + { + "content": "The The evaluation functions: evaluate and\n\nThe function evaluate takes as arguments\n\n```javascript\na program componenta statement or\n\texpressionand an environment.\n```\n\nIt classifies the\ncomponent\nand directs its evaluation.\n\nThe function evaluate\nis structured as a case analysis of the syntactic type of the\ncomponent\nto be evaluated.\n\nIn order to keep the\nfunction\ngeneral, we express\nthe determination of the type of\na component\nabstractly, making no\ncommitment to any particular\ncomponents.\n\nEach type of\ncomponent\nhas a\nsyntax predicate\nthat tests for it and an abstract means for selecting its parts.\n\nThis\nabstract syntax\nmakes it easy to see how we can change the syntax of the language by\nusing the same evaluator, but with a different collection of syntax\nfunctions.", + "token_count": 124, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "The Core of the Evaluator", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_The_Core_of_the_Evaluator_1" + }, + { + "content": "This\nabstract syntax\nmakes it easy to see how we can change the syntax of the language by\nusing the same evaluator, but with a different collection of syntax\nfunctions.\n\n```javascript\nFor\n\t evaluate\n\t returns their value.\n\n\t The function\n\t evaluate\n\t must look up names in the environment to find their values.\n\n\t Combinations\n\n\t For a function application,\n\t evaluate must recursively\n\t evaluate the function expression and the argument expressions of the\n\t application. The resulting function and arguments are passed to\n\n\t An operator combination is transformed into a function application\n\t and then evaluated.\n\n\t Syntactic forms\n\n\t A conditional expression or statement requires special processing of\n\t its parts,\n\t so as to evaluate the consequent if the predicate is true, and\n\t otherwise to evaluate the alternative.\n\n\t A lambda expression must be transformed into an applicable\n\t function by packaging together the parameters and body specified\n\t by the lambda expression with the environment of the evaluation.\n\n\t A sequence of statements requires evaluating its\n\t components in the order in which they appear.\n\n\t A block requires evaluating its body in a new environment\n\t that reflects all names declared within the block.\n\n\t A return statement must produce a value that becomes the\n\t result of the function call that gave rise to the\n\t evaluation of the return statement.\n\n\t A function declaration is transformed into a\n\t constant declaration and then evaluated.\n\n\t A constant or variable declaration or an assignment must\n\t call\n\t evaluate\n\t recursively to compute the new\n\t value to be associated with the name being declared or assigned.\n\t The environment must be\n\t modified to reflect the new value of the name.\n\n\tHere is the declaration of\n\tevaluate:\n\n\t headline_4_1_1\n\n// functions from SICP JS 4.1.1\n\n\t eval\n\t eval_example\n\t 3\n\nfunction evaluate(component, env) {\n return is_literal(component)\n ? literal_value(component)\n : is_name(component)\n ? lookup_symbol_value(symbol_of_name(component), env)\n : is_application(component)\n ? apply(evaluate(function_expression(component), env),\n list_of_values(arg_expressions(component), env))\n : is_operator_combination(component)\n ? evaluate(operator_combination_to_application(component),\n env)\n : is_conditional(component)\n ? eval_conditional(component, env)\n : is_lambda_expression(component)\n ? make_function(lambda_parameter_symbols(component),\n lambda_body(component), env)\n : is_sequence(component)\n ? eval_sequence(sequence_statements(component), env)\n : is_block(component)\n ? eval_block(component, env)\n : is_return_statement(component)\n ? eval_return_statement(component, env)\n : is_function_declaration(component)\n ? evaluate(function_decl_to_constant_decl(component), env)\n : is_declaration(component)\n ? eval_declaration(component, env)\n : is_assignment(component)\n ? eval_assignment(component, env)\n : error(component, \"unknown syntax -- evaluate\");\n}\n\n\t eval_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nconst my_program = parse(\"1; { true; 3; }\");\nevaluate(my_program, the_empty_environment);\n```", + "token_count": 362, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "The Core of the Evaluator", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_The_Core_of_the_Evaluator_2" + }, + { + "content": "This\nabstract syntax\nmakes it easy to see how we can change the syntax of the language by\nusing the same evaluator, but with a different collection of syntax\nfunctions.\n\nFor clarity,\nevaluate\nhas been implemented as a\nconditional expressions.\n\nThe disadvantage of this is that our\nfunction\nhandles only a few distinguishable types of\nstatements and\nexpressions, and no new ones can be defined without editing the\n\n```javascript\ndeclaration of\n\tevaluate.\n```\n\nIn most\ninterpreter\nimplementations, dispatching on the type of\na component\nis done in a data-directed style.\n\nThis allows a user to add new types of\n\n```javascript\ncomponents that\n evaluate\n```\n\ncan distinguish, without modifying the\n\n```javascript\ndeclaration of\n\tevaluate\n```\n\nitself.\n\n(See exercise.)\n\nThe representation of names is handled by the syntax abstractions.\n\nInternally,\nthe evaluator uses strings to represent names, and we refer to such strings as\nsymbols.\n\nThe function\nsymbol_of_name used in\nevaluate extracts from a\nname the symbol by which it is represented.\n\nThe function apply\ntakes two arguments, a\nfunction\nand a list of arguments to which the\nfunction\nshould be applied.\n\nThe function apply\nclassifies\nfunctions\ninto two kinds: It calls\napply_primitive_function\nto apply primitives; it applies compound\nfunctions\n\n```javascript\nby evaluating the block that makes up the body\n\tof the function.\n```\n\nThe environment for the evaluation of the body of a compound\nfunction\nis constructed by extending the base environment carried by the\nfunction\nto include a frame that binds the parameters of the\nfunction\nto the arguments to which the\nfunction\nis to be applied.\n\nHere is the\ndeclaration\nof\n\n```javascript\napply\n apply_example\n 3\n\nfunction apply(fun, args) {\n if (is_primitive_function(fun)) {\n return apply_primitive_function(fun, args);\n } else if (is_compound_function(fun)) {\n const result = evaluate(function_body(fun),\n extend_environment(\n function_parameters(fun),\n args,\n function_environment(fun)));\n return is_return_value(result)\n ? return_value_content(result)\n : undefined;\n } else {\n error(fun, \"unknown function type -- apply\");\n }\n}\n```", + "token_count": 307, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "The Core of the Evaluator", + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_The_Core_of_the_Evaluator_3" + }, + { + "content": "Here is the\ndeclaration\nof\n\n```javascript\napply_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst plus = list(\"primitive\", (x, y) => x + y);\napply(plus, list(1, 2));\n```\n\n```javascript\nIn order to return a value, a JavaScript function needs to evaluate a\n\tundefined is returned.\n\tTo distinguish the two cases, the evaluation of a return statement\n\twill wrap the result of evaluating its return expression into a\n\treturn value. If\n\tthe evaluation of the function body yields such a return value, the content\n\tof the return value is retrieved; otherwise the value\n\tundefined is returned.\n```\n\nWhen processes a function application, it uses list_of_values to produce the list of arguments to which the function is to be applied.\n\n```javascript\nThe function\n list_of_values\n```\n\ntakes as an argument the\nargument expressions of the application.\n\nIt evaluates each\nargument expression\nand returns a\nlist of the corresponding values:\n\n```javascript\nlist_of_values\n list_of_values_example\n\nfunction list_of_values(exps, env) {\n return map(arg => evaluate(arg, env), exps);\n}\n```\n\n```javascript\nlist_of_values_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_addition_expression = parse(\"1 + 2;\");\nlist_of_values(list(parse(\"1;\"), my_addition_expression, parse(\"7;\")),\n the_global_environment);\n```\n\n```javascript\nThe function\n eval_conditional\n```\n\nevaluates the predicate part of\na conditional component\nin the given environment.\n\nIf the result is true,\nthe consequent is evaluated, otherwise the alternative is evaluated:\n\n```javascript\neval_if\n eval_if_example\n 1\n\nfunction eval_conditional(component, env) {\n return is_truthy(evaluate(conditional_predicate(component), env))\n ? evaluate(conditional_consequent(component), env)\n : evaluate(conditional_alternative(component), env);\n}\n```\n\n```javascript\neval_if_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_cond_expr = parse(\"true ? 1 : 2;\");\neval_conditional(my_cond_expr, the_empty_environment);\n```\n\n```javascript\nNote that the evaluator does not need to distinguish between\n\tconditional expressions and conditional statements.\n```\n\nThe use of\nis_truthy\nin\neval_conditional\nconditional_predicate\nis evaluated in the language being implemented and thus yields a value in\nthat language.", + "token_count": 281, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "The Core of the Evaluator", + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_The_Core_of_the_Evaluator_4" + }, + { + "content": "The use of\nis_truthy\nin\neval_conditional\nconditional_predicate\nis evaluated in the language being implemented and thus yields a value in\nthat language.\n\nThe interpreter predicate\nis_truthy\ntranslates that value into a value that can be tested by the\nconditional expression\nin the implementation language: The metacircular representation of truth\nmight not be the same as that of the underlying\nJavaScript.\n\nThe function eval_sequence\nis used by evaluate\nto evaluate a sequence of statements at the top level or in a block.\n\nIt takes as arguments a sequence of statements and an\nenvironment, and evaluates the statements in the order in which they\noccur.\n\nThe value returned is the value of the final statement, except\nthat if the evaluation of any statement in the sequence yields\na return value, that value is returned and the subsequent statements are\nignored.\n\n```javascript\neval_sequence\n\t eval_sequence_example\n\t 3\n\nfunction eval_sequence(stmts, env) {\n if (is_empty_sequence(stmts)) {\n return undefined;\n } else if (is_last_statement(stmts)) {\n return evaluate(first_statement(stmts), env);\n } else {\n const first_stmt_value =\n evaluate(first_statement(stmts), env);\n if (is_return_value(first_stmt_value)) {\n return first_stmt_value;\n } else {\n return eval_sequence(rest_statements(stmts), env);\n }\n }\n}\n```\n\n```javascript\neval_sequence_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nconst my_sequence = head(tail(parse(\"1; true; 3;\")));\neval_sequence(my_sequence, the_empty_environment);\n```\n\nThe function eval_block handles\nblocks.\n\nThe variables and constants (including functions)\ndeclared in the block have the whole block as their scope and thus\nare scanned out before the body of the block is\nevaluated.\n\nThe body of the block is evaluated with respect to an environment\nthat extends the current\nenvironment by a frame that binds each local name\nto a special value,\n\"*unassigned*\".\n\nThis string serves as a placeholder, before\nthe evaluation of the declaration assigns the name\nits proper value.", + "token_count": 280, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "The Core of the Evaluator", + "chunk_index": 5, + "chunk_id": "Metalinguistic_Abstraction_The_Core_of_the_Evaluator_5" + }, + { + "content": "This string serves as a placeholder, before\nthe evaluation of the declaration assigns the name\nits proper value.\n\nAn attempt to access the value of the name before its\ndeclaration is evaluated leads to an error at run time (see\nexercise ), as stated in\nfootnote in chapter.\n\n```javascript\neval_block\n scan_out_declarations\n\t eval_block_example\n\t 42\n\nfunction eval_block(component, env) {\n const body = block_body(component);\n const locals = scan_out_declarations(body);\n const unassigneds = list_of_unassigned(locals);\n return evaluate(body, extend_environment(locals,\n unassigneds,\n env));\n}\nfunction list_of_unassigned(symbols) {\n return map(symbol => \"*unassigned*\", symbols);\n}\n```\n\n```javascript\nlist_of_unassigned\n\nfunction list_of_unassigned(symbols) {\n return map(symbol => \"*unassigned*\", symbols);\n}\n```\n\nThe function scan_out_declarations declaration_symbol to retrieve the symbol that represents the name from the declaration statements it finds.\n\n```javascript\nscan_out_declarations\n\t scan_out_declarations_example\n\t [ 'x', [ 'y', null ] ]\n\nfunction scan_out_declarations(component) {\n return is_sequence(component)\n ? accumulate(append,\n null,\n map(scan_out_declarations,\n sequence_statements(component)))\n : is_declaration(component)\n ? list(declaration_symbol(component))\n : null;\n}\n```\n\n```javascript\nscan_out_declarations_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nscan_out_declarations(parse(\"const x = 1; let y = 2;\"));\n```\n\n```javascript\neval_block_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nconst my_block = parse(\"{ const x = 1; 3; 42; }\");\neval_block(my_block, the_empty_environment);\n```\n\nWe ignore declarations that are nested in another block,\nbecause the evaluation of that block will take care of them.\n\nThe function scan_out_declarations\nlooks for declarations only in sequences because\ndeclarations in conditional statements, function declarations, and\nlambda expressions are always in a nested block.\n\nThe function eval_return_statement\nis used to evaluate\napply and\nthe evaluation\nof sequences, the result of evaluation of a return statement\nneeds to be identifiable so that the evaluation of a function\nbody can return immediately, even if there are statements\nafter the return statement.\n\nFor this purpose,\nthe evaluation of a return statement wraps the result of\nevaluating the return expression in a return value object.\n\n```javascript\neval_return\n\t eval_return_example\n\t 1\n\nfunction eval_return_statement(component, env) {\n return make_return_value(evaluate(return_expression(component),\n env));\n}\n```", + "token_count": 303, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "The Core of the Evaluator", + "chunk_index": 6, + "chunk_id": "Metalinguistic_Abstraction_The_Core_of_the_Evaluator_6" + }, + { + "content": "For this purpose,\nthe evaluation of a return statement wraps the result of\nevaluating the return expression in a return value object.\n\n```javascript\neval_return_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nconst my_program = parse(\"{ function f() { return 1; } f(); }\");\nevaluate(my_program, the_global_environment);\n```\n\nThe function eval_assignment handles assignments to\n\n```javascript\nnames.\n\t(To simplify the\tpresentation of our evaluator,\n\twe are allowing assignment not just to variables but\n\talsoerroneouslyto constants.\n\tExercise\n\texplains how we could\n\tdistinguish constants from variables and prevent\n\tassignment to constants.)\n```\n\n```javascript\nThe function\n\teval_assignment\n\tcalls\n\tassignment_symbol\n\tto retrieve the symbol that represents the name\n\tfrom the assignment. The function\n\teval_assignment\n\ttransmits the symbol and the value to\n\tassign_symbol_value\n\tto be installed in the designated environment.\n\tThe evaluation of an assignment returns the value\n\tthat was assigned.\n```\n\n```javascript\neval_assignment\n eval_assignment_example\n 2\n\nfunction eval_assignment(component, env) {\n const value = evaluate(assignment_value_expression(component),\n env);\n assign_symbol_value(assignment_symbol(component), value, env);\n return value;\n}\n```\n\n```javascript\neval_assignment_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_program = parse(\"{ let x = 1; x = 2; }\");\nevaluate(my_program, the_global_environment);\n```\n\nConstant and variable declarations are both recognized by the\nis_declaration syntax predicate.\n\nThey are treated in a manner similar to\nassignments, because eval_block\nhas already bound their symbols to \"*unassigned*\"\nin the current environment.\n\nTheir evaluation replaces \"*unassigned*\"\nwith the result of evaluating the value expression.\n\n```javascript\neval_definition\n\t eval_definition_example\n\t 3\n\nfunction eval_declaration(component, env) {\n assign_symbol_value(\n declaration_symbol(component),\n evaluate(declaration_value_expression(component), env),\n env);\n return undefined;\n}\n```\n\nThe result of evaluating the body of a function is determined by\nreturn statements, and therefore the return value\nundefined in\neval_declaration only\nmatters when the declaration occurs at the top level,\noutside of any function body.\n\nHere we use the return value\nundefined to simplify\nthe presentation; exercise\ndescribes the real result of evaluating top-level components\nin JavaScript.", + "token_count": 294, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "The Core of the Evaluator", + "chunk_index": 7, + "chunk_id": "Metalinguistic_Abstraction_The_Core_of_the_Evaluator_7" + }, + { + "content": "Here we use the return value\nundefined to simplify\nthe presentation; exercise\ndescribes the real result of evaluating top-level components\nin JavaScript.\n\n```javascript\neval_definition_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nconst my_program = parse(\"{ let x = 1; const y = 2; x + y; }\");\nevaluate(my_program, the_global_environment);\n```", + "token_count": 47, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "The Metacircular Evaluator", + "subsection": "The Core of the Evaluator", + "chunk_index": 8, + "chunk_id": "Metalinguistic_Abstraction_The_Core_of_the_Evaluator_8" + }, + { + "content": "The preceding chapters introduced the basic elements from which\nprograms are made.\n\nWe saw how primitive\nfunctions\nand primitive data are combined to construct compound entities, and we\nlearned that abstraction is vital in helping us to cope with the complexity\nof large systems.\n\nBut these tools are not sufficient for designing\nprograms.\n\nEffective program synthesis also requires organizational\nprinciples that can guide us in formulating the overall design of a\nprogram.\n\nIn particular, we need strategies to help us structure large\nsystems so that they will be\nmodular , that is, so that they can\nbe divided naturally into coherent parts that can be\nseparately developed and maintained.\n\nOne powerful design strategy, which is particularly appropriate to the construction of programs for\n\nTo a large extent, then, the way we organize a large program is\ndictated by our perception of the system to be modeled.\n\nIn this\nchapter we will investigate two prominent organizational strategies\narising from two rather different world views of the\nstructure of systems.\n\nThe first organizational strategy concentrates on\nobjects , viewing a large system as a collection of distinct objects\nwhose behaviors may change over time.\n\nAn alternative organizational\nstrategy concentrates on the\nstreams of information that flow in\nthe system, much as an electrical engineer views a signal-processing\nsystem.\n\nBoth the object-based approach and the stream-processing approach\nraise significant linguistic issues in programming.\n\nWith objects, we must be concerned with how a computational object can\nchange and yet maintain its identity.\n\nThis will force us to abandon\nour old substitution model of computation\n(section ) in favor of a more\nmechanistic but less theoretically tractable\nenvironment model of\ncomputation.\n\nThe difficulties of dealing with objects, change, and\nidentity are a fundamental consequence of the need to grapple with\ntime in our computational models.", + "token_count": 300, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": null, + "subsection": null, + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_Modularity_Objects_and_State_1" + }, + { + "content": "The difficulties of dealing with objects, change, and\nidentity are a fundamental consequence of the need to grapple with\ntime in our computational models.\n\nThese difficulties become even\ngreater when we allow the possibility of concurrent execution of\nprograms.\n\nThe stream approach can be most fully exploited when we\ndecouple simulated time in our model from the order of the events that\ntake place in the computer during evaluation.\n\nWe will accomplish this\nusing a technique known as\ndelayed evaluation.", + "token_count": 80, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": null, + "subsection": null, + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_Modularity_Objects_and_State_2" + }, + { + "content": "We ordinarily view the world as populated by independent objects, each\nof which has a state that changes over time.\n\nAn object is said to\nhave state if its behavior is influenced by its history.\n\nA bank account, for example, has state in that the answer to the question\nCan I withdraw 100? depends upon the history of\ndeposit and withdrawal transactions.\n\nWe can characterize an\nobject s state by one or more\nstate variables , which among them maintain enough\ninformation about history to determine the object s current behavior.\n\nIn a simple banking system, we could characterize the state of an\naccount by a current balance rather than by remembering the entire\nhistory of account transactions.\n\nIn a system composed of many objects, the objects are rarely\ncompletely independent.\n\nEach may influence the states of others\nthrough interactions, which serve to couple the state variables of one\nobject to those of other objects.\n\nIndeed, the view that a system is\ncomposed of separate objects is most useful when the state variables\nof the system can be grouped into closely coupled subsystems that are\nonly loosely coupled to other subsystems.\n\nThis view of a system can be a powerful framework for organizing\ncomputational models of the system.\n\nFor such a model to be modular,\nit should be decomposed into computational objects that model the\nactual objects in the system.\n\nEach computational object must have its\nown local state variables describing the actual object s\nstate.\n\nSince the states of objects in the system being modeled change over\ntime, the state variables of the corresponding computational objects\nmust also change.", + "token_count": 270, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_Assignment_and_Local_State_1" + }, + { + "content": "Since the states of objects in the system being modeled change over\ntime, the state variables of the corresponding computational objects\nmust also change.\n\nIf we choose to model the flow of time in the\nsystem by the elapsed time in the computer, then we must have a way to\nconstruct computational objects whose behaviors change as our programs\nrun.\n\nIn particular, if we wish to model state variables by ordinary\nsymbolic names in the programming language, then the language must\nprovide an\nassignment operation\nto enable us to change the value\nassociated with a name.", + "token_count": 96, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_Assignment_and_Local_State_2" + }, + { + "content": "To illustrate what we mean by having a computational object with\nfunction\nInsufficient funds.\n\nFor example, if we begin with 100\nin the account, we should obtain the following sequence of responses\nusing\n\n```javascript\nwithdraw_example\n 75\n withdraw\n\nwithdraw(25);\n```\n\n```javascript\nwithdraw_example2\n withdraw\n withdraw_example\n 50\n\nwithdraw(25);\n```\n\n```javascript\nwithdraw_example3\n withdraw\n withdraw_example\n withdraw_example2\n 'Insufficient funds'\n\nwithdraw(60);\n```\n\n```javascript\nwithdraw_example4\n withdraw\n withdraw_example\n withdraw_example2\n withdraw_example3\n 35\n\nwithdraw(15);\n```\n\nObserve that the expression\nwithdraw(25),\nevaluated twice, yields different values.\n\nThis is a new kind of\nbehavior for a\nfunction.\n\nUntil now, all our\nJavaScript functions\ncould be viewed as specifications for computing mathematical functions.\n\nA call to a\nfunction\ncomputed the value of the function applied to the given arguments,\nand two calls to the same\nfunction\nwith the same arguments always produced the same\nresult.\n\n```javascript\nSo far, all our names have been immutable.\n\tWhen a function was applied, the values that its parameters\n\treferred to never changed, and once a declaration was evaluated,\n\tthe declared name never changed its value.\n To implement functions like\n\tvariable declarations, which use the keyword\n\tlet, in addition to constant\n\tdeclarations, which use the keyword\n\tconst.\n We can declare a variable\n\tbalance\n\tto indicate the balance of money\n\tin the account and define\n```\n\nThe\nfunction\nchecks to see if Insufficient funds\nmessage.\n\nHere are the\ndeclarations\nof\n\n```javascript\nwithdraw\n withdraw_example\n\nlet balance = 100;\n\nfunction withdraw(amount) {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n}\n```\n\nDecrementing statement\n\n```javascript\nbalance = balance - amount;\n```\n\n```javascript\nThe syntax of\n\tassignment expressions is\n\nname = new-value\n```\n\nHere\n\n```javascript\nname\n\thas been declared with\n\tlet or\n\tas a\n```\n\nand\nnew-value\nis any expression.\n\nThe assignment\nchanges\nname\nso that its value is the\nresult obtained by evaluating\nnew-value.\n\nIn the case at hand, we are changing", + "token_count": 311, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": "Local State Variables", + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_Local_State_Variables_1" + }, + { + "content": "In the case at hand, we are changing\n\n```javascript\nThe function withdraw also uses a\n\tsequence of statements to cause two statements to be evaluated\n\tin the case where the if test is\n\ttrue: first decrementing balance\n\tand then returning the value of\n\tbalance.\n\tIn general, executing a sequence\n\nstmt$_{1}$ stmt$_{2} \\ldots$stmt$_{n}$\n\n causes the statements stmt$_{1}$\n\tthrough\n\tstmt$_{n}$ to be evaluated in\n\tsequence.\n```\n\nAlthough\nprogram\nenvironment and is freely accessible to be examined or\nmodified by any\nfunction.\n\nIt would be much better if we could somehow make\nfunction\nthat could access\nfunction\ncould access\n\nWe can make\n\n```javascript\nnew_withdraw_example\n\nnew_withdraw(60);\nnew_withdraw(60);\n```\n\n```javascript\nnew_withdraw\n new_withdraw_example\n\nfunction make_withdraw_balance_100() {\n let balance = 100;\n return amount => {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n };\n}\nconst new_withdraw = make_withdraw_balance_100();\n```\n\n```javascript\nWhat we have done here is use let\n\tto establish an environment with a local variable\n\tbalance, bound to the initial\n\tvalue 100. Within this local environment, we use a lambda\n\texpressionamount\n\tas an argument and behaves like our previous\n\twithdraw function. This\n\tfunctionreturned as the result of evaluating the body of the\n\tmake_withdraw_balance_100\n\tfunctionbehaves in precisely the same way as\n\twithdraw, but its variable\n\tbalance is not accessible by any\n\tother function.\n```\n\nCombining\nassignments with variable declarations\nis the general programming\ntechnique we will use for constructing computational objects with\nlocal state.\n\nUnfortunately, using this technique raises a serious\nproblem: When we first introduced\nfunctions,\nwe also introduced the substitution model of evaluation\n(section ) to provide an\ninterpretation of what\nfunction\napplication means.\n\nWe said that applying a\nfunction whose body is a return statement\nshould be interpreted as evaluating the\nreturn expression of the function\nwith the\nparameters replaced by their values.", + "token_count": 301, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": "Local State Variables", + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_Local_State_Variables_2" + }, + { + "content": "We said that applying a\nfunction whose body is a return statement\nshould be interpreted as evaluating the\nreturn expression of the function\nwith the\nparameters replaced by their values.\n\n```javascript\nFor functions with more complex\n\tbodies, we need to evaluate the whole body with the\n\tparameters replaced by their values.\n```\n\nThe trouble is that,\nas soon as we introduce assignment into our language, substitution is no\nlonger an adequate model of\nfunction\napplication.\n\n(We will see why this is so in\nsection.) As a consequence, we\ntechnically have at this point no way to understand why the\nnew_withdraw\nfunction\nbehaves as claimed above.\n\nIn order to really understand a\nfunction\nsuch as\nnew_withdraw,\nwe will need to develop a new model of\nfunction\napplication.\n\nIn section we will\nintroduce such a model, together with an explanation of\nassignments and variable declarations.\n\nFirst, however, we examine some variations on the theme established by\nnew_withdraw.\n\n```javascript\nParameters of functions as well as names declared with\n\tlet are\n```\n\nThe following\nfunction, make_withdraw,\ncreates withdrawal processors.\n\nThe parameter\nmake_withdraw\nspecifies the initial amount of money in the\naccount.\n\n```javascript\nmake_withdraw\n make_withdraw_define\n\nfunction make_withdraw(balance) {\n return amount => {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n };\n}\n```\n\nThe function make_withdraw can be used as follows to create two objects\n\n```javascript\nmake_withdraw_define\n make_withdraw\n\nconst W1 = make_withdraw(100);\nconst W2 = make_withdraw(100);\n```\n\n```javascript\nmake_withdraw_example1\n make_withdraw_define\n 50\n\nW1(50);\n```\n\n```javascript\nmake_withdraw_example1\n make_withdraw_example2\n 30\n\nW2(70);\n```\n\n```javascript\nmake_withdraw_example3\n make_withdraw_example2\n 'Insufficient funds'\n\nW2(40);\n```\n\n```javascript\nmake_withdraw_example4\n make_withdraw_example3\n 10\n\nW1(40);\n```\n\nObserve that\n\nWe can also create objects that handle function that returns a bank-account object with a specified initial balance:", + "token_count": 288, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": "Local State Variables", + "chunk_index": 3, + "chunk_id": "Modularity_Objects_and_State_Local_State_Variables_3" + }, + { + "content": "We can also create objects that handle function that returns a bank-account object with a specified initial balance:\n\n```javascript\nmake_account\n make_account_example_my\n\nfunction make_account(balance) {\n function withdraw(amount) {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n }\n function deposit(amount) {\n balance = balance + amount;\n return balance;\n }\n function dispatch(m) {\n return m === \"withdraw\"\n ? withdraw\n : m === \"deposit\"\n ? deposit\n : error(m, \"unknown request -- make_account\");\n }\n return dispatch;\n}\n```\n\n```javascript\nmake_account_example_my\n\nconst acc = make_account(100);\n\nacc(\"withdraw\")(50);\n```\n\nEach call to\nfunctions\nfunction\nmessage as input and returns one of the two local\nfunctions.\n\nThe\nfunction\nitself is returned as the value that represents the bank-account object.\n\nThis is precisely the\nmessage-passing style of programming that we saw in\nsection , although here we are using\nit in conjunction with the ability to modify local variables.\n\n```javascript\nThe function\n\tmake_account\n```\n\ncan be used as follows:\n\n```javascript\nmake_account_example\n make_account\n\nconst acc = make_account(100);\n```\n\n```javascript\nmake_account_example1\n make_account_example\n 50\n\nacc(\"withdraw\")(50);\n```\n\n```javascript\nmake_account_example1\n make_account_example2\n 'Insufficient funds'\n\nacc(\"withdraw\")(60);\n```\n\n```javascript\nmake_account_example2\n make_account_example3\n 90\n\nacc(\"deposit\")(40);\n```\n\n```javascript\nmake_account_example3\n make_account_example4\n 30\n\nacc(\"withdraw\")(60);\n```\n\nEach call to function, which is then applied to the specified\n\n```javascript\nmake_withdraw, another\n\tcall to make_account\n```\n\n```javascript\nmake_account\n\nconst acc2 = make_account(100);\n```\n\nwill produce a completely separate account object, which maintains its own local", + "token_count": 230, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": "Local State Variables", + "chunk_index": 4, + "chunk_id": "Modularity_Objects_and_State_Local_State_Variables_4" + }, + { + "content": "As we shall see, introducing assignment into our programming language\nleads us into a thicket of difficult conceptual issues.\n\nNevertheless,\nviewing systems as\nfunction\n\nIt is not at all clear what is meant by chosen at random.\n\nWhat we presumably want is for successive calls to\nfunction\nrand_update\nthat has the property that if we start with a given number\n$x_{1}$ and form\n\n```javascript\n$x_2$ = rand_update($x_1$);\n$x_3$ = rand_update($x_2$);\n```\n\nthen the sequence of values $x_1, x_2, x_3, \\ldots$ , will have the desired statistical properties.\n\nWe can implement\nfunction\nwith a local state variable\nrandom_init.\n\nEach call to\nrand_update\nof the current value of\n\n```javascript\nrand_update\n\n// A very simple rand_update function computes a number\n// from 0 (inclusive) to 200560490131 (a large prime)\n// from a value x by multiplying it with a constant a,\n// adding a constant b, and then taking the remainder\n// of dividing it by the large prime. We used it here\n// for illustration only, and do not claim any\n// statistical properties.\nconst m = 200560490131;\nconst a = 1103515245;\nconst b = 12345;\n\nfunction rand_update(x) {\n return (a * x + b) % m;\n}\n```\n\n```javascript\nrand_definition\n rand_update\n random_init\n rand_example\n 40083849805\n\nfunction make_rand() {\n let x = random_init;\n return () => {\n x = rand_update(x);\n return x;\n };\n}\nconst rand = make_rand();\n```\n\n```javascript\nrandom_init\n\nconst random_init = 123456789;\n```\n\n```javascript\nrand_example\n\ndisplay(rand());\ndisplay(rand());\ndisplay(rand());\n\nrand();\nrand();\nrand();\n```\n\nOf course, we could generate the same sequence of random numbers\nwithout using assignment by simply calling\nrand_update\ndirectly.\n\nHowever, this would mean that any part of our program that used\nrandom numbers would have to explicitly remember the current value of\nrand_update.\n\nTo realize what an annoyance this would be, consider using random numbers\nto implement a technique called\nMonte Carlo simulation.", + "token_count": 305, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": "The Benefits of Introducing Assignment", + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_The_Benefits_of_Introducing_Assignment_1" + }, + { + "content": "To realize what an annoyance this would be, consider using random numbers\nto implement a technique called\nMonte Carlo simulation.\n\nThe Monte Carlo method consists of choosing sample experiments at random\nfrom a large set and then making deductions on the basis of the\nprobabilities estimated from tabulating the results of those experiments.\n\nFor example, we can approximate\n$\\pi$ using the fact that\n$6/\\pi^2$ is the probability that two integers\nchosen at random will have no factors in common; that is, that their\ngreatest common divisor will be 1. $\\pi$ , we perform\na large number of experiments.\n\nIn each experiment we choose two integers at\nrandom and perform a test\n$6/\\pi^2$ , and from this\nwe obtain our approximation to $\\pi$.\n\nThe heart of our program is a\nfunction\nmonte_carlo,\nwhich takes as arguments the number of times to try an experiment, together\nwith the experiment, represented as a no-argument\nfunction\nthat will return either true or false each time it is run.\n\nThe function\nruns the experiment for the designated number of trials and returns a\nnumber telling the fraction of the trials in which the experiment was\nfound to be true.\n\n```javascript\nmonte_carlo\n rand_definition\n gcd_definition\n estimate_pi_example\n 3.1408877612819492\n\nfunction estimate_pi(trials) {\n return math_sqrt(6 / monte_carlo(trials, dirichlet_test));\n}\nfunction dirichlet_test() {\n return gcd(rand(), rand()) === 1;\n}\nfunction monte_carlo(trials, experiment) {\n function iter(trials_remaining, trials_passed) {\n return trials_remaining === 0\n ? trials_passed / trials\n : experiment()\n ? iter(trials_remaining - 1, trials_passed + 1)\n : iter(trials_remaining - 1, trials_passed);\n }\n return iter(trials, 0);\n}\n```\n\n```javascript\nestimate_pi_example\n\nestimate_pi(10000);\n```\n\nNow let us try the same computation using rand_update directly rather than", + "token_count": 270, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": "The Benefits of Introducing Assignment", + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_The_Benefits_of_Introducing_Assignment_2" + }, + { + "content": "Now let us try the same computation using rand_update directly rather than\n\n```javascript\nestimate_pi_alternative\n rand_definition\n gcd_definition\n estimate_pi_example\n 3.1408877612819492\n\nfunction estimate_pi(trials) {\n return math_sqrt(6 / random_gcd_test(trials, random_init));\n}\nfunction random_gcd_test(trials, initial_x) {\n function iter(trials_remaining, trials_passed, x) {\n const x1 = rand_update(x);\n const x2 = rand_update(x1);\n return trials_remaining === 0\n ? trials_passed / trials\n : gcd(x1, x2) === 1\n ? iter(trials_remaining - 1, trials_passed + 1, x2)\n : iter(trials_remaining - 1, trials_passed, x2);\n }\n return iter(trials, 0, initial_x);\n}\n```\n\nWhile the program is still simple, it betrays some painful breaches of\nmodularity.\n\nIn our first version of the program, using\nmonte_carlo\nfunction\nthat takes as an argument an arbitrary\nfunction.\n\nIn our second version of the program, with no local state for the\nrandom-number generator,\nrandom_gcd_test\nmust explicitly manipulate the random numbers\nrand_update.\n\nThis explicit handling of the random numbers intertwines the structure of\naccumulating test results with the fact that our particular experiment uses\ntwo random numbers, whereas other Monte Carlo experiments might use one\nrandom number or three.\n\nEven the top-level\nfunction\nestimate_pi\nhas to be concerned with supplying an initial random number.\n\nThe fact that\nthe random-number generator s insides are leaking out into other parts\nof the program makes it difficult for us to isolate the Monte Carlo idea so\nthat it can be applied to other tasks.\n\nIn the first version of the program,\nassignment encapsulates the state of the random-number generator within the\nfunction,\nso that the details of random-number generation remain independent of the\nrest of the program.\n\nThe general phenomenon illustrated by the Monte Carlo example is this: From\nthe point of view of one part of a complex process, the other parts appear\nto change with time.\n\nThey have hidden time-varying local state.", + "token_count": 291, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": "The Benefits of Introducing Assignment", + "chunk_index": 3, + "chunk_id": "Modularity_Objects_and_State_The_Benefits_of_Introducing_Assignment_3" + }, + { + "content": "They have hidden time-varying local state.\n\nIf we wish\nto write computer programs whose structure reflects this decomposition, we\nmake computational objects (such as bank accounts and random-number\ngenerators) whose behavior changes with time.\n\nWe model state with local\nstate variables, and we model the changes of state with assignments to those\nvariables.\n\nIt is tempting to conclude this discussion by saying that, by introducing\nassignment and the technique of hiding state in local variables, we are able\nto structure systems in a more modular fashion than if all state had to be\nmanipulated explicitly, by passing additional parameters.\n\nUnfortunately,\nas we shall see, the story is not so simple.", + "token_count": 110, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": "The Benefits of Introducing Assignment", + "chunk_index": 4, + "chunk_id": "Modularity_Objects_and_State_The_Benefits_of_Introducing_Assignment_4" + }, + { + "content": "As we have seen,\nassignment\nenables us to model objects\nthat have local state.\n\nHowever, this advantage comes at a price.\n\nOur\nprogramming language can no longer be interpreted in terms of the\nsubstitution model of\nfunction\napplication that we introduced in\nsection.\n\nMoreover, no simple\nmodel with nice mathematical properties can be an adequate\nframework for dealing with objects and assignment in programming languages.\n\nSo long as we do not use assignments, two evaluations of the same\nfunction\nwith the same arguments will produce the same result, so that\nfunctions\ncan be viewed as computing mathematical functions.\n\nProgramming without any\nuse of assignments, as we did throughout the first two chapters of this\nbook, is accordingly known as\nfunctional programming.\n\nfunction of section that does not bother to check for an insufficient amount:\n\n```javascript\nmake_simplified_withdraw\n make_simplified_withdraw_example\n\nfunction make_simplified_withdraw(balance) {\n return amount => {\n balance = balance - amount;\n return balance;\n };\n}\n```\n\n```javascript\nmake_simplified_withdraw_example\n make_simplified_withdraw\n\nconst W = make_simplified_withdraw(25);\n```\n\n```javascript\nmake_simplified_withdraw_example1\n make_simplified_withdraw\n make_simplified_withdraw_example\n 5\n\nW(20);\n```\n\n```javascript\nmake_simplified_withdraw_example2\n make_simplified_withdraw\n make_simplified_withdraw_example\n make_simplified_withdraw_example1\n -5\n\nW(10);\n```\n\nCompare this function with the following function, which does not use assignment:\n\n```javascript\nmake_decrementer\n\nfunction make_decrementer(balance) {\n return amount => balance - amount;\n}\n```\n\n```javascript\nThe function\n make_decrementer\n```\n\nreturns a function that subtracts its input from a designated amount\n\n```javascript\nmake_decrementer\n make_decrementer_example\n\nconst D = make_decrementer(25);\n```\n\n```javascript\nmake_decrementer_example\n make_decrementer_example1\n 5\n\nD(20);\n```\n\n```javascript\nmake_decrementer_example1\n make_decrementer_example2\n 15\n\nD(10);\n```\n\nWe can use the substitution model to explain how\n\n```javascript\nmake_decrementer\n 5\n\nmake_decrementer(25)(20)\n\nmake_decrementer(25)(20);\n```\n\nWe first simplify the\nfunction expression of the application\nby substituting\n$25$ for\nmake_decrementer.\n\nThis reduces the\nexpression to\n\n```javascript\n(amount => 25 - amount)(20)\n```\n\nNow we apply the function by substituting 20 for lambda expression:\n\n```javascript\n5\n\n25 - 20\n\n25 - 20;\n```\n\nThe final answer is 5.\n\nObserve, however, what happens if we attempt a similar substitution analysis with\n\n```javascript\nmake_simplified_withdraw\n\nmake_simplified_withdraw(25)(20)\n```", + "token_count": 320, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": "The Costs of Introducing Assignment", + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_The_Costs_of_Introducing_Assignment_1" + }, + { + "content": "Observe, however, what happens if we attempt a similar substitution analysis with\n\nWe first simplify the function expression by substituting 25 for the body of expression to\n\n```javascript\n(amount => {\n balance = 25 - amount;\n return 25;\n })(20)\n```\n\nNow we apply the function by substituting 20 for lambda expression:\n\n```javascript\nbalance = 25 - 20;\nreturn 25;\n```\n\nIf we adhered to the substitution model, we would have to say that the meaning of the function application is to first set assignment) from\n\nthe second occurrence of assignment), and the substitution model cannot do this.\n\nThe trouble here is that substitution is based ultimately on the notion that\n\n```javascript\nthe name in our language are essentially\n\tsymbols for values.\n```\n\n```javascript\nThis worked well for constants.\n\tBut a variable, whose value can change with assignment, cannot simply\n\tbe a name for a value. A variable somehow refers to a place where a\n\tvalue can be stored, and the value stored at this place can change.\n```\n\nIn section we will see how environments play this role of place in our computational model.\n\nThe issue surfacing here is more profound than the mere breakdown of a\nparticular model of computation.\n\nAs soon as we introduce change into\nour computational models, many notions that were previously\nstraightforward become problematical.\n\nConsider the concept of two\nthings being the same.\n\nSuppose we call make_decrementer twice with the same argument to create two functions:\n\n```javascript\nmake_decrementer\n\nconst D1 = make_decrementer(25);\n\nconst D2 = make_decrementer(25);\n```\n\nAre\neach is a\nfunction\nthat subtracts its input from 25.\n\nIn fact,\n\nContrast this with making two calls to make_simplified_withdraw:\n\n```javascript\nmake_simplified_withdraw\n make_simplified_withdraw_example3\n\nconst W1 = make_simplified_withdraw(25);\n\nconst W2 = make_simplified_withdraw(25);\n```\n\nAre\n\n```javascript\nmake_simplified_withdraw_example3\n make_simplified_withdraw_example4\n 5\n\nW1(20);\n```\n\n```javascript\nmake_simplified_withdraw_example4\n make_simplified_withdraw_example5\n -15\n\nW1(20);\n```\n\n```javascript\nmake_simplified_withdraw_example5\n make_simplified_withdraw_example6\n 5\n\nW2(20);\n```", + "token_count": 304, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": "The Costs of Introducing Assignment", + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_The_Costs_of_Introducing_Assignment_2" + }, + { + "content": "Are\n\nEven though equal in the sense that they are both created by evaluating the same expression, make_simplified_withdraw(25), it is not true that\n\nA language that supports the concept that equals can be substituted\nfor equals in an expression without changing the value of the\nexpression is said to be\nreferentially transparent.\n\nReferential transparency is violated\nwhen we include\nassignment\nin our computer language.\n\nThis makes it tricky to determine when we can\nsimplify expressions by substituting equivalent expressions.\n\nConsequently,\nreasoning about programs that use assignment becomes drastically more\ndifficult.\n\nOnce we forgo referential transparency, the notion of what it means for\ncomputational objects to be the same becomes difficult to\ncapture in a formal way.\n\nIndeed, the meaning of same in the\nreal world that our programs model is hardly clear in itself.\n\nIn general,\nwe can determine that two apparently identical objects are indeed\nthe same one only by modifying one object and then observing\nwhether the other object has changed in the same way.\n\nBut how can we tell\nif an object has changed other than by observing the\nsame object twice and seeing whether some property of the\nobject differs from one observation to the next?\n\nThus, we cannot determine\nchange without some a priori notion of\nsameness, and we cannot determine sameness without observing\nthe effects of change.\n\nAs an example of how this issue arises in programming, consider the\nsituation where Peter and Paul have a\n100 in\nit.\n\nThere is a substantial difference between modeling this as\n\n```javascript\nmake_account\n 50\n\nconst peter_acc = make_account(100);\nconst paul_acc = make_account(100);\n\nconst peter_acc = make_account(100);\nconst paul_acc = make_account(100);\n\npeter_acc(\"withdraw\")(10);\npeter_acc(\"withdraw\")(20);\npaul_acc(\"withdraw\")(50);\n```\n\nand modeling it as\n\n```javascript\nmake_account\n 20\n\nconst peter_acc = make_account(100);\nconst paul_acc = peter_acc;\n\nconst peter_acc = make_account(100);\nconst paul_acc = peter_acc;\npeter_acc(\"withdraw\")(10);\npeter_acc(\"withdraw\")(20);\npaul_acc(\"withdraw\")(50);\n```", + "token_count": 304, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": "The Costs of Introducing Assignment", + "chunk_index": 3, + "chunk_id": "Modularity_Objects_and_State_The_Costs_of_Introducing_Assignment_3" + }, + { + "content": "and modeling it as\n\nIn the first situation, the two bank accounts are distinct.\n\nTransactions made by Peter will not affect Paul s account, and vice\nversa.\n\nIn the second situation, however, we have defined\npaul_acc\nto be the same thing as\npeter_acc.\n\nIn effect, Peter and Paul now have a joint bank account, and if Peter makes\na withdrawal from\npeter_acc\nPaul will observe less money in\npaul_acc.\n\nThese two similar but distinct situations can cause confusion in building\ncomputational models.\n\nWith the shared account, in particular, it can be\nespecially confusing that there is one object (the bank account) that has\ntwo different names\n\n```javascript\n(peter_acc and\n\tpaul_acc);\n```\n\nif we are searching for all the places in our program where paul_acc can be changed, we must remember to look also at things that\n\nchange peter_acc.\n\nWith reference to the above remarks on sameness and\nchange, observe that if Peter and Paul could only examine\ntheir bank balances, and could not perform operations that changed the\nbalance, then the issue of whether the two accounts are distinct would be\nmoot.\n\nIn general, so long as we never modify data objects, we can regard a\ncompound data object to be precisely the totality of its pieces.\n\nFor\nexample, a rational number is determined by giving its numerator and\nits denominator.\n\nBut this view is no longer valid in the presence of\nchange, where a compound data object has an identity that is\nsomething different from the pieces of which it is composed.\n\nA bank\naccount is still the same bank account even if we change the\nbalance by making a withdrawal; conversely, we could have two\ndifferent bank accounts with the same state information.", + "token_count": 285, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": "The Costs of Introducing Assignment", + "chunk_index": 4, + "chunk_id": "Modularity_Objects_and_State_The_Costs_of_Introducing_Assignment_4" + }, + { + "content": "A bank\naccount is still the same bank account even if we change the\nbalance by making a withdrawal; conversely, we could have two\ndifferent bank accounts with the same state information.\n\nThis\ncomplication is a consequence, not of our programming language, but of\nour perception of a bank account as an object.\n\nWe do not, for\nexample, ordinarily regard a rational number as a changeable object\nwith identity, such that we could change the numerator and still have\nthe same rational number.\n\nIn contrast to functional programming, programming that makes extensive use\nof assignment is known as\nimperative programming.\n\nIn addition to raising complications about\ncomputational models, programs written in imperative style are susceptible\nto bugs that cannot occur in functional programs.\n\nFor example, recall the\niterative factorial program from\n\n```javascript\nsection\n\t(here using a conditional statement instead of a conditional\n\texpression):\n```\n\n```javascript\nfactorial_iterative\n factorial_example\n 120\n\nfunction factorial(n) {\n function iter(product, counter) {\n if (counter > n) {\n return product;\n } else {\n return iter(counter * product,\n counter + 1);\n }\n }\n return iter(1, 1);\n}\n```\n\nInstead of passing arguments in the internal iterative loop, we could adopt a more imperative style by using explicit assignment to update the values of\n\nthe variables\n\n```javascript\nfactorial_imperative\n factorial_example\n 120\n\nfunction factorial(n) {\n let product = 1;\n let counter = 1;\n function iter() {\n if (counter > n) {\n return product;\n } else {\n product = counter * product;\n counter = counter + 1;\n return iter();\n }\n }\n return iter();\n}\n```\n\nThis does not change the results produced by the program, but it does\nintroduce a subtle trap.\n\nHow do we decide the order of the assignments?\n\nAs it happens, the program is correct as written.\n\nBut writing the\nassignments in the opposite order\n\n```javascript\ncounter = counter + 1;\nproduct = counter * product;\n```", + "token_count": 307, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": "The Costs of Introducing Assignment", + "chunk_index": 5, + "chunk_id": "Modularity_Objects_and_State_The_Costs_of_Introducing_Assignment_5" + }, + { + "content": "But writing the\nassignments in the opposite order\n\nwould have produced a different,\n\nThe complexity of imperative programs becomes even worse if we consider\napplications in which several processes execute concurrently.\n\nWe will\nreturn to this in section.\n\nFirst, however, we will address the issue of providing a computational\nmodel for expressions that involve assignment, and explore the uses of\nobjects with local state in designing simulations.", + "token_count": 67, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": "The Costs of Introducing Assignment", + "chunk_index": 6, + "chunk_id": "Modularity_Objects_and_State_The_Costs_of_Introducing_Assignment_6" + }, + { + "content": "We ve seen the power of computational objects with local state as\ntools for modeling.\n\nYet, as\nsection\nwarned, this power extracts a price: the loss of referential\ntransparency, giving rise to a thicket of questions about sameness and\nchange, and the need to abandon the substitution model of evaluation in\nfavor of the more intricate environment model.\n\nThe central issue lurking beneath the complexity of state, sameness,\nand change is that by introducing assignment we are forced to admit\ntime into our computational models.\n\nBefore we introduced\nassignment, all our programs were timeless, in the sense that any\nexpression that has a value always has the same value.\n\nIn contrast,\nrecall the example of modeling withdrawals from a bank account\nand returning the resulting balance,\nintroduced at the beginning of\nsection :\n\n```javascript\nwithdraw_example_3_4\n withdraw\n 75\n\nwithdraw(25);\n```\n\n```javascript\nwithdraw_example_3_4_second\n withdraw\n withdraw_example_3_4\n 50\n\nwithdraw(25);\n```\n\nHere successive evaluations of the same expression yield different\nvalues.\n\nThis behavior arises from the fact that the execution of\nassignments (in this case, assignments to the variable\nmoments in time\nwhen values change.\n\nThe result of evaluating an expression depends not\nonly on the expression itself, but also on whether the evaluation occurs\nbefore or after these moments.\n\nBuilding models in terms of computational\nobjects with local state forces us to confront time as an essential concept\nin programming.\n\nWe can go further in structuring computational models to match our\nperception of the physical world.\n\nObjects in the world do not change\none at a time in sequence.\n\nRather we perceive them as acting\nconcurrently all at once.\n\nSo it is often natural to\nmodel systems as collections of\nthreads (sequences of computational steps)\nthat execute concurrently.", + "token_count": 284, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_Concurrency_Time_Is_of_the_Essence_1" + }, + { + "content": "So it is often natural to\nmodel systems as collections of\nthreads (sequences of computational steps)\nthat execute concurrently.\n\nJust as we can make our programs modular by\norganizing models in terms of objects with separate local state, it is\noften appropriate to divide computational models into parts that evolve\nseparately and concurrently.\n\nEven if the programs are to be executed on\na sequential computer, the practice of writing programs as if they were\nto be executed concurrently forces the programmer to avoid inessential\ntiming constraints and thus makes programs more modular.\n\nIn addition to making programs more modular, concurrent computation\ncan provide a speed advantage over sequential computation.\n\nSequential\ncomputers execute only one operation at a time, so the amount of time\nit takes to perform a task is proportional to the total number of\noperations performed.\n\nUnfortunately, the complexities introduced by assignment become even\nmore problematic in the presence of concurrency.\n\nThe fact of\nconcurrent execution, either because the world operates in parallel or\nbecause our computers do, entails additional complexity in our\nunderstanding of time.", + "token_count": 178, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_Concurrency_Time_Is_of_the_Essence_2" + }, + { + "content": "We ve seen that the difficulty in dealing with concurrent\nthreads\nis rooted in the need to consider the interleaving of the order of events\nin the different\nthreads.\n\nFor example, suppose we have two\nthreads ,\none with three ordered events $(a,b,c)$\nand one with three ordered events $(x,y,z)$.\n\nIf the two\nthreads\nrun concurrently, with no constraints on how their execution is\ninterleaved, then there are 20 different possible orderings for the events\nthat are consistent with the individual orderings for the two\nthreads :\n\\[ \\begin{array}{cccc}\n(a,b,c,x,y,z) & (a,x,b,y,c,z) & (x,a,b,c,y,z) & (x,a,y,z,b,c)\\\\\n(a,b,x,c,y,z) & (a,x,b,y,z,c) & (x,a,b,y,c,z) & (x,y,a,b,c,z)\\\\\n(a,b,x,y,c,z) & (a,x,y,b,c,z) & (x,a,b,y,z,c) & (x,y,a,b,z,c)\\\\\n(a,b,x,y,z,c) & (a,x,y,b,z,c) & (x,a,y,b,c,z) & (x,y,a,z,b,c)\\\\\n(a,x,b,c,y,z) & (a,x,y,z,b,c) & (x,a,y,b,z,c) & (x,y,z,a,b,c)\n\\end{array} \\]\nAs programmers designing this system, we would have to consider the\neffects of each of these 20 orderings and check that each behavior is\nacceptable.\n\nSuch an approach rapidly becomes unwieldy as the numbers of\nthreads and events increase.\n\nA more practical approach to the design of concurrent systems is to\ndevise general mechanisms that allow us to constrain the interleaving\nof concurrent\nthreads\nso that we can be sure that the program\nbehavior is correct.\n\nMany mechanisms have been developed for this\npurpose.\n\nIn this section, we describe one of them, the\nserializer.\n\nSerialization implements the following idea:\nThreads\nwill execute concurrently, but there will be certain collections of\nfunctions\nthat cannot be executed concurrently.\n\nMore precisely, serialization\ncreates distinguished sets of\nfunctions\nsuch that only one execution of a\nfunction\nin each serialized set is permitted to happen at a time.", + "token_count": 269, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": "Mechanisms for Controlling Concurrency", + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_1" + }, + { + "content": "More precisely, serialization\ncreates distinguished sets of\nfunctions\nsuch that only one execution of a\nfunction\nin each serialized set is permitted to happen at a time.\n\nIf some\nfunction\nin the set is being executed, then a\nthread\nthat attempts to execute any\nfunction\nin the set will be forced to wait\nuntil the first execution has finished.\n\nWe can use serialization to control access to shared variables.\n\nFor example, if we want to update a shared variable based on the\nprevious value of that variable, we put the access to the previous\nvalue of the variable and the assignment of the new value to the\nvariable in the same\nfunction.\n\nWe then ensure that no other\nfunction\nthat assigns to the variable can run concurrently with this\nfunction\nby serializing all of these\nfunctions\nwith the same serializer.\n\nThis guarantees that the value of the\nvariable cannot be changed between an access and the corresponding\nassignment.\n\nTo make the above mechanism more concrete, suppose that we have extended JavaScript to include a function called concurrent_execute:\n\n```javascript\nconcurrent_execute($f_{1}$, $f_{2}$, $\\ldots$, $f_{k}$)\n```\n\n```javascript\nEach $f$ must be a function of no arguments.\n The function concurrent_execute\n creates a separate thread for each\n $f$, which applies\n $f$ (to no arguments).\n```\n\nThese threads all run concurrently.\n\nAs an example of how this is used, consider\n\n```javascript\nconcurrent_execute_example\n 'all threads terminated'\n\nlet x = 10;\n\nconcurrent_execute(() => { x = x * x; },\n () => { x = x + 1; });\n```\n\n```javascript\nThis creates two concurrent\n\tthreads$T_1$, which sets\n\t$T_2$,\n\twhich increments $T_1$ and $T_2$:\n\n 101:\n\t $T_1$\n sets $T_2$ increments\n\n 121:\n\t $T_2$ increments\n\t $T_1$ sets\n\n 110:\n\t $T_2$ changes\n $T_1$\n\n\t accesses the value of\n x * x.\n\n 11:\n\t $T_2$ accesses\n\t $T_1$ sets $T_2$ sets\n\n 100:\n\t $T_1$ accesses\n\t $T_2$ sets\n\t $T_1$ sets\n```", + "token_count": 302, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": "Mechanisms for Controlling Concurrency", + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_2" + }, + { + "content": "As an example of how this is used, consider\n\nWe can constrain the concurrency by using serialized\nfunctions,\nwhich are created by serializers.\n\nSerializers are constructed by\nmake_serializer,\nwhose implementation is given below.\n\nA serializer takes a\nfunction\nas argument and returns a serialized\nfunction\nthat behaves like the original\nfunction.\n\nAll calls to a given serializer return serialized\nfunctions\nin the same set.\n\nThus, in contrast to the example above, executing\n\n```javascript\nserializer_example\n serializer\n 'all threads terminated'\n\nlet x = 10;\n\nconst s = make_serializer();\n\nconcurrent_execute(s(() => { x = x * x; }),\n s(() => { x = x + 1; }));\n```\n\ncan produce only two possible values for\n\n```javascript\n$T_1$ and\n $T_2$ cannot be interleaved.\n```\n\nHere is a version of the make_account function from section , where the deposits and withdrawals have been\n\n```javascript\nmake_account_serialize_attempt_example\n\nconst my_account = make_account(100);\n\nconcurrent_execute(\n () => {\n display(my_account(\"balance\"), \"T1 balance\");\n my_account(\"withdraw\")(50);\n display(my_account(\"balance\"), \"T1 balance\");\n my_account(\"deposit\")(100);\n },\n () => { display(my_account(\"balance\"), \"T2 balance\");\n my_account(\"withdraw\")(50);\n display(my_account(\"balance\"), \"T2 balance\");\n my_account(\"deposit\")(100);\n }\n);\n```\n\n```javascript\nmake_account_serialize_attempt\n serializer\n make_account_serialize_attempt_example\n\nfunction make_account(balance) {\n function withdraw(amount) {\n if (balance > amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n }\n function deposit(amount) {\n balance = balance + amount;\n return balance;\n }\n const protect = make_serializer();\n function dispatch(m) {\n return m === \"withdraw\"\n ? protect(withdraw)\n : m === \"deposit\"\n ? protect(deposit)\n : m === \"balance\"\n ? balance\n : error(m, \"unknown request -- make_account\");\n }\n return dispatch;\n}\n```\n\nWith this implementation, two\nthreads\ncannot be withdrawing from or\ndepositing into a single account concurrently.\n\nThis eliminates the source\nof the error illustrated in figure ,\nwhere Peter changes the account balance between the times when Paul accesses\nthe balance to compute the new value and when Paul actually performs the\nassignment.", + "token_count": 301, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": "Mechanisms for Controlling Concurrency", + "chunk_index": 3, + "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_3" + }, + { + "content": "This eliminates the source\nof the error illustrated in figure ,\nwhere Peter changes the account balance between the times when Paul accesses\nthe balance to compute the new value and when Paul actually performs the\nassignment.\n\nOn the other hand, each account has its own serializer,\nso that deposits and withdrawals for different accounts can proceed\nconcurrently.\n\nSerializers provide a powerful abstraction that helps isolate the\ncomplexities of concurrent programs so that they can be dealt with\ncarefully and (hopefully) correctly.\n\nHowever, while using serializers\nis relatively straightforward when there is only a single shared\nresource (such as a single bank account), concurrent programming can\nbe treacherously difficult when there are multiple shared resources.\n\nTo illustrate one of the difficulties that can arise, suppose we wish to\n\n```javascript\nexchange_example\n\nconst a1 = make_account(100);\nconst a2 = make_account(200);\nconst a3 = make_account(300);\n\nconcurrent_execute(\n () => {\n display(a1(\"balance\"), \"Peter balance a1 before\");\n display(a2(\"balance\"), \"Peter balance a2 before\");\n exchange(a1, a2);\n display(a1(\"balance\"), \"Peter balance a1 after\");\n display(a2(\"balance\"), \"Peter balance a2 after\");\n },\n () => {\n display(a1(\"balance\"), \"Paul balance a1 before\");\n display(a3(\"balance\"), \"Paul balance a3 before\");\n exchange(a1, a3);\n display(a1(\"balance\"), \"Paul balance a1 after\");\n display(a3(\"balance\"), \"Paul balance a3 after\");\n }\n);\n```\n\n```javascript\nexchange\n make_account_serialize_attempt\n exchange_example\n\nfunction exchange(account1, account2) {\n const difference = account1(\"balance\") - account2(\"balance\");\n account1(\"withdraw\")(difference);\n account2(\"deposit\")(difference);\n}\n```\n\nThis\nfunction\nworks well when only a single\nthread\nis trying to do the exchange.\n\nSuppose, however, that Peter and Paul both\nhave access to accounts $a_1$ ,\n$a_2$ , and $a_3$ , and\nthat Peter exchanges $a_1$ and\n$a_2$ while Paul concurrently exchanges\n$a_1$ and $a_3$.", + "token_count": 262, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": "Mechanisms for Controlling Concurrency", + "chunk_index": 4, + "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_4" + }, + { + "content": "Suppose, however, that Peter and Paul both\nhave access to accounts $a_1$ ,\n$a_2$ , and $a_3$ , and\nthat Peter exchanges $a_1$ and\n$a_2$ while Paul concurrently exchanges\n$a_1$ and $a_3$.\n\nEven with account deposits and withdrawals\nserialized for individual accounts (as in the\nmake_account\nfunction\nshown above in this section), $a_1$ and\n$a_2$ , but then Paul might change the balance in\n$a_1$ before Peter is able to complete the\nexchange.\nfunction\nto lock out any other concurrent accesses to the accounts during the\nentire time of the exchange.\n\nOne way we can accomplish this is by using both accounts serializers\nto serialize the entire function.\n\nTo do this, we will arrange for access to an account s serializer.\n\nNote that we are deliberately breaking the modularity of the bank-account\nobject by exposing the serializer.\n\nThe following version of\nmake_@account\nis identical to the original version given in\nsection , except that a\nserializer is provided to protect the balance variable, and the serializer\nis exported via message passing:\n\n```javascript\nmake_account_and_serializer\n serializer\n 'all threads terminated'\n\nfunction make_account_and_serializer(balance) {\n function withdraw(amount) {\n if (balance > amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n }\n function deposit(amount) {\n balance = balance + amount;\n return balance;\n }\n const balance_serializer = make_serializer();\n return m => m === \"withdraw\"\n ? withdraw\n : m === \"deposit\"\n ? deposit\n : m === \"balance\"\n ? balance\n : m === \"serializer\"\n ? balance_serializer\n : error(m, \"unknown request -- make_account\");\n}\n```\n\nWe can use this to do serialized deposits and withdrawals.\n\nHowever,\nunlike our earlier serialized account, it is now the responsibility of\neach user of bank-account objects to explicitly manage the\nserialization, for example as\nfollows:\n\n```javascript\nmake_account_and_serializer_example\n\nconst my_account = make_account_and_serializer(100);\n\ndeposit(my_account, 200);\n\ndisplay(my_account(\"balance\"));\n```", + "token_count": 298, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": "Mechanisms for Controlling Concurrency", + "chunk_index": 5, + "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_5" + }, + { + "content": "However,\nunlike our earlier serialized account, it is now the responsibility of\neach user of bank-account objects to explicitly manage the\nserialization, for example as\nfollows:\n\n```javascript\nmake_account_and_serializer_usage\n make_account_and_serializer\n make_account_and_serializer_example\n\nfunction deposit(account, amount) {\n const s = account(\"serializer\");\n const d = account(\"deposit\");\n s(d(amount));\n}\n```\n\nExporting the serializer in this way gives us enough flexibility to\nimplement a serialized exchange program.\n\nWe simply serialize the original\nfunction\nwith the serializers for both accounts:\n\n```javascript\nserialized_exchange_example\n\nconst a1 = make_account_and_serializer(100);\nconst a2 = make_account_and_serializer(200);\nconst a3 = make_account_and_serializer(300);\n\nconcurrent_execute(\n () => {\n display(a1(\"balance\"), \"Peter balance a1 before\");\n display(a2(\"balance\"), \"Peter balance a2 before\");\n serialized_exchange(a1, a2);\n display(a1(\"balance\"), \"Peter balance a1 after\");\n display(a2(\"balance\"), \"Peter balance a2 after\");\n },\n () => {\n display(a1(\"balance\"), \"Paul balance a1 before\");\n display(a3(\"balance\"), \"Paul balance a3 before\");\n serialized_exchange(a1, a3);\n display(a1(\"balance\"), \"Paul balance a1 after\");\n display(a3(\"balance\"), \"Paul balance a3 after\");\n }\n);\n```\n\n```javascript\nserialized_exchange\n make_account_and_serializer\n exchange\n serialized_exchange_example\n\nfunction serialized_exchange(account1, account2) {\n const serializer1 = account1(\"serializer\");\n const serializer2 = account2(\"serializer\");\n serializer1(serializer2(exchange))(account1, account2);\n}\n```\n\nWe implement serializers in terms of a more primitive synchronization\nmechanism called a\nmutex.\n\nA mutex is an object that supports two\noperations the mutex can be\nacquired , and the mutex can be\nreleased.\n\nOnce a mutex has been acquired, no other acquire\noperations on that mutex may proceed until the mutex is\nreleased.\n\n```javascript\nfunction\n\tf,\n```\n\nthe serializer returns a\nfunction\nthat acquires the mutex, runs\nf,\nand then releases the mutex.\n\nThis ensures that only one of the\nfunctions\nproduced by the serializer can be running at once, which is\nprecisely the serialization property that we need to guarantee.", + "token_count": 266, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": "Mechanisms for Controlling Concurrency", + "chunk_index": 6, + "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_6" + }, + { + "content": "This ensures that only one of the\nfunctions\nproduced by the serializer can be running at once, which is\nprecisely the serialization property that we need to guarantee.\n\n```javascript\nTo apply serializers to functions that take an arbitrary number of arguments,\n\twe use JavaScript's rest parameter and spread syntax.\n\t... in front of the\n\tparameter args collects\n\tthe rest (here all) of the arguments of any call of the function\n\tinto a vector data structure.\n\tThe\n\t... in front of\n\targs in\n\tthe application\n\tf(...args)\n\tspreads the elements of\n\targs so that\n\tthey become separate arguments of\n\tf.\n```\n\n```javascript\nserializer\n mutex\n 'all threads terminated'\n\nfunction make_serializer() {\n const mutex = make_mutex();\n return f => {\n function serialized_f(...args) {\n mutex(\"acquire\");\n const val = f(...args);\n mutex(\"release\");\n return val;\n }\n return serialized_f;\n };\n}\n\nfunction make_serializer() {\n const mutex = make_mutex();\n return f => {\n function serialized_f() {\n mutex(\"acquire\");\n const val = f();\n mutex(\"release\");\n return val;\n }\n return serialized_f;\n };\n}\n```\n\nThe mutex is a mutable object (here we ll use a one-element list,\nwhich we ll refer to as a\ncell ) that can hold the value true or false.\n\nWhen the value is\nfalse, the mutex is available to be acquired.\n\nWhen the value is true, the\nmutex is unavailable, and any\nthread\nthat attempts to acquire the mutex must wait.\n\nOur mutex constructor\nmake_mutex\nbegins by initializing the cell contents to false.\n\nTo acquire the mutex,\nwe test the cell.\n\nIf the mutex is available, we set the cell contents to\ntrue and proceed.\n\nOtherwise, we wait in a loop, attempting to acquire over\nand over again, until we find that the mutex is available.", + "token_count": 277, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": "Mechanisms for Controlling Concurrency", + "chunk_index": 7, + "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_7" + }, + { + "content": "Otherwise, we wait in a loop, attempting to acquire over\nand over again, until we find that the mutex is available.\n\n```javascript\nmutex\n 'all threads terminated'\n\nfunction make_mutex() {\n const cell = list(false);\n function the_mutex(m) {\n return m === \"acquire\"\n ? test_and_set(cell)\n ? the_mutex(\"acquire\") // retry\n : true\n : m === \"release\"\n ? clear(cell)\n : error(m, \"unknown request -- mutex\");\n }\n return the_mutex;\n}\nfunction clear(cell) {\n set_head(cell, false);\n}\n```\n\nThe function test_and_set\ntests the cell and returns the result of the test.\n\nIn addition, if the\ntest was false,\ntest_and_set\nsets the cell contents to true before returning false.\n\nWe can express this\nbehavior as the following\nfunction:\n\n```javascript\ntest_and_set\n 'all threads terminated'\n\nfunction test_and_set(cell) {\n if (head(cell)) {\n return true;\n } else {\n set_head(cell, true);\n return false;\n }\n}\n```\n\nHowever, this implementation of\ntest_and_set\ndoes not suffice as it stands.\n\nThere is a crucial subtlety here, which is\nthe essential place where concurrency control enters the system: The\ntest_and_set\noperation must be performed\natomically.\n\nThat is, we must guarantee that, once a\nthread\nhas tested the cell and found it to be false, the cell contents will\nactually be set to true before any other\nthread\ncan test the cell.\n\nIf we do not make this guarantee, then the mutex can\nfail in a way similar to the bank-account failure in\nfigure.\n\n(See\nexercise.)\n\nThe actual implementation of\ntest_and_set\ndepends on the details of how our system runs concurrent\nthreads.\n\nFor example, we might be executing concurrent\nthreads\non a sequential processor using a\nthreads,\npermitting each\nthread\nto run for a short time before interrupting it\nand moving on to the next\nthread.\n\nIn that case,\ntest_and_set\ncan work by disabling time slicing during the testing and\nsetting.", + "token_count": 293, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": "Mechanisms for Controlling Concurrency", + "chunk_index": 8, + "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_8" + }, + { + "content": "In that case,\ntest_and_set\ncan work by disabling time slicing during the testing and\nsetting.\n\nAlternatively, multiprocessing computers provide instructions that\nsupport atomic operations directly in hardware.\n\nNow that we have seen how to implement serializers, we can see\nthat account exchanging still has a problem, even with the\nserialized_exchange\nfunction\nabove.\n\nImagine that Peter attempts to exchange $a_1$\nwith $a_2$ while Paul concurrently attempts to\nexchange $a_2$ with\n$a_1$.\n\nSuppose that Peter s\nthread\nreaches the point where it has entered a serialized\nfunction\nprotecting $a_1$ and, just after that,\nPaul s\nthread\nenters a serialized\nfunction\nprotecting $a_2$.\n\nNow Peter cannot proceed (to\nenter a serialized\nfunction\nprotecting $a_2$ ) until Paul exits the serialized\nfunction\nprotecting $a_2$.\n\nSimilarly, Paul cannot proceed\nuntil Peter exits the serialized\nfunction\nprotecting $a_1$.\n\nEach\nthread\nis stalled forever, waiting for the other.\n\nThis situation is called a\ndeadlock.\n\nDeadlock is always a danger in systems that provide\nconcurrent access to multiple shared resources.\n\nOne way to avoid the\nserialized_exchange\nso that a\nthread\nwill always attempt to enter a\nfunction\nprotecting the lowest-numbered account first.\n\nAlthough this method works\nwell for the exchange problem, there are other situations that require more\nsophisticated deadlock-avoidance techniques, or where deadlock cannot\nbe avoided at all.\n\n(See exercises\nand.)\n\nWe ve seen how programming concurrent systems requires controlling\nthe ordering of events when different\nthreads\naccess shared state, and we ve seen how to achieve this control\nthrough judicious use of serializers.\n\nBut the problems of concurrency\nlie deeper than this, because, from a fundamental point of view, it s\nnot always clear what is meant by shared state.\n\nMechanisms such as\ntest_and_set\nrequire\nthreads\nto examine a global shared flag at arbitrary times.", + "token_count": 289, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": "Mechanisms for Controlling Concurrency", + "chunk_index": 9, + "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_9" + }, + { + "content": "Mechanisms such as\ntest_and_set\nrequire\nthreads\nto examine a global shared flag at arbitrary times.\n\nThis is problematic\nand inefficient to implement in modern high-speed processors, where\ndue to optimization techniques such as pipelining and cached memory,\nthe contents of memory may not be in a consistent state at every instant.\n\nIn\nsome\nmultiprocessing systems, therefore, the serializer paradigm\nis being supplanted by\nother\napproaches to concurrency\ncontrol.\n\nThe problematic aspects of shared state also arise in large, distributed\nsystems.\n\nFor instance, imagine a distributed banking system where\nindividual branch banks maintain local values for bank balances and\nperiodically compare these with values maintained by other branches.\n\nIn\nsuch a system the value of the account balance would be\nundetermined, except right after synchronization.\n\nIf Peter deposits money\nin an account he holds jointly with Paul, when should we say that the\naccount balance has changed when the balance in the local branch\nchanges, or not until after the synchronization?\n\nAnd if Paul accesses the\naccount from a different branch, what are the reasonable constraints to\nplace on the banking system such that the behavior is\ncorrect ?\n\nThe only thing that might matter for correctness\nis the behavior observed by Peter and Paul individually and the\nstate of the account immediately after synchronization.\n\nQuestions about the real account balance or the order of\nevents between synchronizations may be irrelevant or\nmeaningless.\n\nThe basic phenomenon here is that synchronizing different threads, establishing shared state, or imposing an order on events requires communication among the threads.", + "token_count": 255, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": "Mechanisms for Controlling Concurrency", + "chunk_index": 10, + "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_10" + }, + { + "content": "On the surface, time seems straightforward.\n\nIt\nis an ordering imposed on events. $A$ and\n$B$ ,\neither $A$ occurs before\n$B$ ,\n$A$ and\n$B$ are simultaneous, or\n$A$ occurs after\n$B$.\n\nFor instance,\nreturning to the bank account example, suppose that Peter withdraws\n10 and Paul withdraws 25 from a\n100, leaving 65 in the account.\n\nDepending on the\norder of the two withdrawals, the sequence of balances in the account is\neither $\\$100 \\rightarrow \\$90 \\rightarrow\\$65$\nor $\\$100 \\rightarrow \\$75 \\rightarrow\\$65$.\n\nIn a computer implementation of the banking system, this changing\nsequence of balances could be modeled by successive assignments to\na variable\n\nIn complex situations, however, such a view can be problematic.\n\nSuppose that Peter and Paul, and other people besides, are\naccessing the same bank account through a network of banking machines\ndistributed all over the world.\n\nThe actual sequence of balances in\nthe account will depend critically on the detailed timing of the\naccesses and the details of the communication among the machines.\n\nThis threads sharing a common variable thread specified by the function given in section :\n\n```javascript\nwithdraw_concurrency\n balance_concurrency\n withdraw_example_concurrency\n 75\n\nfunction withdraw(amount) {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n}\n```\n\n```javascript\nbalance_concurrency\n\nlet balance = 100;\n```\n\n```javascript\nwithdraw_example_concurrency\n\nwithdraw(25); // output: 75\n```\n\nIf the two threads operate independently, then Peter might test the balance and attempt to withdraw a legitimate amount. s test.\n\nThings can be worse still.\n\nConsider the\nstatement\n\n```javascript\nbalance = balance - amount;\n```\n\nexecuted as part of each withdrawal process.\n\nThis consists of three\nsteps: (1) accessing the value of the s withdrawals execute this statement concurrently, then the\ntwo withdrawals might interleave the order in which they access", + "token_count": 299, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": "The Nature of Time in Concurrent Systems", + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_The_Nature_of_Time_in_Concurrent_Systems_1" + }, + { + "content": "This consists of three\nsteps: (1) accessing the value of the s withdrawals execute this statement concurrently, then the\ntwo withdrawals might interleave the order in which they access\n\nThe timing diagram in\nfigure\ndepicts\nan order of events where s assignment of 75 to\n100.\n\nAfterwards, Peter has 10, Paul has\n25, and the bank has 75.\n\nThe general phenomenon illustrated\nhere is that several\nthreads\nmay\nthread\nmay be trying to manipulate the shared state at the same\ntime.\n\nFor the bank account example, during each transaction, each\ncustomer should be able to act as if the other customers did not\nexist.\n\nWhen\ncustomers change\nthe balance in a way that depends on\nthe balance,\nthey\nmust be able to assume that, just before the moment of\nchange, the balance is still what\nthey\nthought it was.\n\nThe above example typifies the subtle bugs that can creep into\nconcurrent programs.\n\nThe root of this complexity lies in the\nassignments to variables that are shared among the different\nthreads.\n\nWe already know that we must be careful in writing programs that use\nassignment,\nbecause the results of a computation depend on the order in which the\nassignments occur. threads\nwe must be especially careful about\nassignments, because we may not be able to control the order of the\nassignments made by the different\nthreads.\n\nIf several such changes\nmight be made concurrently (as with two depositors accessing a joint\naccount) we need some way to ensure that our system behaves correctly.\n\nFor example, in the case of withdrawals from a joint bank account, we\nmust ensure that money is conserved.\n\nTo make concurrent programs behave correctly, we may have to\nplace some restrictions on concurrent execution.", + "token_count": 287, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": "The Nature of Time in Concurrent Systems", + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_The_Nature_of_Time_in_Concurrent_Systems_2" + }, + { + "content": "To make concurrent programs behave correctly, we may have to\nplace some restrictions on concurrent execution.\n\nOne possible restriction on concurrency would stipulate that no two\noperations that change any shared state variables can occur at the same\ntime.\n\nThis is an extremely stringent requirement.\n\nFor distributed banking,\nit would require the system designer to ensure that only one transaction\ncould proceed at a time.\n\nThis would be both inefficient and overly\nconservative.\n\nFigure shows\nPeter and Paul sharing a bank account, where Paul has a private account\nas well.\n\nThe diagram illustrates two withdrawals from the shared account\n(one by Peter and one by Paul) and a deposit to Paul s private\naccount. s deposit and\nwithdrawal must not be concurrent (since both access and update the amount\nin Paul s wallet).\n\nBut there should be no problem permitting\nPaul s deposit to his private account to proceed concurrently with\nPeter s withdrawal from the shared account.\n\nConcurrent deposits and withdrawals from a\njoint account in Bank1 and a private account in Bank2.\n\nA less stringent restriction on concurrency would ensure that a\nconcurrent system produces the same result as if the\nthreads\nhad run sequentially in some order.\n\nThere are two important aspects to this\nrequirement.\n\nFirst, it does not require the\nthreads\nto actually run sequentially, but only to produce results that are the same\nas if they had run sequentially.\n\nFor the example in\nfigure , the designer of the\nbank account system can safely allow Paul s deposit and Peter s\nwithdrawal to happen concurrently, because the net result will be the same as\nif the two operations had happened sequentially.", + "token_count": 276, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": "The Nature of Time in Concurrent Systems", + "chunk_index": 3, + "chunk_id": "Modularity_Objects_and_State_The_Nature_of_Time_in_Concurrent_Systems_3" + }, + { + "content": "For the example in\nfigure , the designer of the\nbank account system can safely allow Paul s deposit and Peter s\nwithdrawal to happen concurrently, because the net result will be the same as\nif the two operations had happened sequentially.\n\nSecond, there may be more\nthan one possible correct result produced by a concurrent\nprogram, because we require only that the result be the same as for\nsome sequential order.\n\nFor example, suppose that Peter and\nPaul s joint account starts out with 100, and Peter deposits\n40 while Paul concurrently withdraws half the money in the account.\n\nThen sequential execution could result in the account balance being either\n70 or 90 (see\nexercise ).\n\nThere are still weaker requirements for correct execution of concurrent\nprograms.\n\nA program for simulating\nthreads,\neach one representing a small volume of space, that update their values\nconcurrently.\n\nEach\nthread\nrepeatedly changes its value to the average of its own value and its\nneighbors values.\n\nThis algorithm converges to the right answer\nindependent of the order in which the operations are done; there is no\nneed for any restrictions on concurrent use of the shared values.", + "token_count": 194, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": "The Nature of Time in Concurrent Systems", + "chunk_index": 4, + "chunk_id": "Modularity_Objects_and_State_The_Nature_of_Time_in_Concurrent_Systems_4" }, { - "chapter_file": "chapter3_chunks.json", - "section": null, - "subsection": "Modularity, Objects, and State", - "chunk_id": "chapter3_chunks_Modularity,_Objects,_and_State_2", - "chunk_index": 2, - "content": "This will force us to abandon our old substitution model of computation (section ) in favor of a more mechanistic but less theoretically tractable environment model of computation. The difficulties of dealing with objects, change, and identity are a fundamental consequence of the need to grapple with time in our computational models. These difficulties become even greater when we allow the possibility of concurrent execution of programs. The stream approach can be most fully exploited when we decouple simulated time in our model from the order of the events that take place in the computer during evaluation. We will accomplish this using a technique known as delayed evaluation .", - "token_count": 120, - "source_files": [ - "chapter3.xml" - ] + "content": "Chapter dealt with compound data as a means for constructing\ncomputational objects that have several parts, in order to model\nreal-world objects that have several aspects.\n\nIn that chapter we\nintroduced the discipline of data abstraction, according to which data\nstructures are specified in terms of constructors, which create data\nobjects, and selectors, which access the parts of compound data\nobjects.\n\nBut we now know that there is another aspect of data that\nchapter did not address.\n\nThe desire to model systems composed of\nobjects that have changing state leads us to the need to modify\ncompound data objects, as well as to construct and select from them.\n\nIn order to model compound objects with changing state, we will design\ndata abstractions to include, in addition to selectors and\nconstructors, operations called\nmutators , which modify data\nobjects.\n\nFor instance, modeling a banking system requires us to\nchange account balances.\n\nThus, a data structure for representing bank\naccounts might admit an operation\n\n```javascript\nset_balance(account, new-value)\n```\n\nthat changes the balance of the designated account to the designated\nnew value.\n\nData objects for which mutators are defined are known as\nmutable data objects.\n\nChapter introduced pairs as a general-purpose glue\nfor synthesizing compound data.\n\nWe begin this section by defining basic\nmutators for pairs, so that pairs can serve as building blocks for\nconstructing mutable data objects.\n\nThese mutators greatly enhance the\nrepresentational power of pairs, enabling us to build data structures\nother than the sequences and trees that we worked with in\nsection.\n\nWe also present some\nexamples of simulations in which complex systems are modeled as collections\nof objects with local state.", + "token_count": 273, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_Modeling_with_Mutable_Data_1" }, { - "chapter_file": "chapter3_chunks.json", + "content": "Designing complex digital systems, such as computers, is an important\nengineering activity.\n\nDigital systems are constructed by\ninterconnecting simple elements.\n\nAlthough the behavior of these\nindividual elements is simple, networks of them can have very complex\nbehavior.\n\nComputer simulation of proposed circuit designs is an\nimportant tool used by digital systems engineers.\n\nIn this section we\ndesign a system for performing digital logic simulations.\n\nThis system\ntypifies a kind of program called an\nevent-driven simulation , in\nwhich actions ( events ) trigger further events that happen\nat a later time, which in turn trigger more events, and so on.\n\nOur computational model of a circuit will be composed of objects that\ncorrespond to the elementary components from which the circuit is\nconstructed.\n\nThere are\nwires , which carry\ndigital signals.\n\nA digital signal may at any moment have only one of two\npossible values,\n0 and 1.\n\nThere are also various types of digital\nfunction boxes , which connect wires carrying input signals to other output\nwires.\n\nSuch boxes produce output signals computed from their input\nsignals.\n\nThe output signal is\ninverter is a\nprimitive function box that inverts its input.\n\nIf the\ninput signal to an inverter changes to 0, then one inverter-delay\nlater the inverter will change its output signal to 1.\n\nIf the input\nsignal to an inverter changes to 1, then one inverter-delay later\nthe inverter will change its output signal to 0.\n\nWe draw an inverter\nsymbolically as in figure.\n\nAn\nand-gate ,\nalso shown in figure , is a primitive\nfunction box with two inputs and one output.\n\nIt drives its output signal\nto a value that is the\nlogical and of the inputs.", + "token_count": 281, + "has_code": false, + "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", - "subsection": "Representing Tables", - "chunk_id": "chapter3_chunks_Representing_Tables_1", + "subsection": "A Simulator for Digital Circuits", "chunk_index": 1, - "content": "When we studied various ways of representing sets in chapter , we mentioned in section the task of maintaining a table of records , we made extensive use of two-dimensional tables, in which information is stored and retrieved using two keys. Here we see how to build tables as mutable list structures. We first consider a car s head s point to successive records. These gluing pairs are called the backbone of the table. In order to have a place that we can change when we add a new record to the table, we build the table as a headed list . A headed list has a special backbone pair at the beginning, which holds a dummy record in this case the arbitrarily chosen symbol *table* . string \"*table*\" . Figure Figure shows the box-and-pointer diagram for the table a: 1 b: 2 c: 3 a: 1 b: 2 c: 3 A table represented as a headed list. A table represented as a headed list. To extract information from a table we use the lookup procedure, function, which takes a key as argument and returns the associated value (or false undefined if there is no value stored under that key). Lookup The function lookup is defined in terms of the assoc operation, which expects a key and a list of records as arguments. Note that assoc never sees the dummy record. Assoc The function assoc returns the record that has the given key as its car head .", - "token_count": 284, - "source_files": [ - "subsection3.xml" - ] + "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_1" }, { - "chapter_file": "chapter3_chunks.json", + "content": "It drives its output signal\nto a value that is the\nlogical and of the inputs.\n\nThat is, if both\nof its input signals become 1, then one and-gate-delay time\nlater the and-gate will force its output signal to be 1; otherwise the\noutput will be 0.\n\nAn\nor-gate is a similar two-input primitive function\nbox that drives its output signal to a value that is the\nlogical or of the inputs.\n\nThat is, the output will become 1 if at least one\nof the input signals is 1; otherwise the output will become 0.\n\nWe can connect primitive functions together to construct more complex\nfunctions.\n\nTo accomplish this we wire the outputs of some\nfunction boxes to the inputs of other function boxes.\n\nFor example,\nthe\nhalf-adder circuit shown in\nfigure consists of an\nor-gate, two and-gates, and an inverter.\n\nIt takes two input signals,\n$A$ and $B$ , and has\ntwo output signals, $S$ and $C$.\n$S$ will become 1\nwhenever precisely one of $A$ and $B$\nis 1, and $C$ will become 1 whenever\n$A$ and $B$ are both 1.\n\nWe can see\nfrom the figure that, because of the\ndelays involved, the outputs may be generated at different times.\n\nMany of the difficulties in the design of digital circuits arise from\nthis fact.\n\nA half-adder circuit.\n\nWe will now build a program for modeling the digital logic circuits we\nwish to study.\n\nThe program will construct computational objects\nmodeling the wires, which will hold the signals.\n\nFunction\nboxes will be modeled by\nfunctions\nthat enforce the correct relationships among the signals.\n\nOne basic element of our simulation will be a\nfunction\nmake_wire,\nwhich constructs wires.\n\nFor example, we can construct six wires as follows:", + "token_count": 288, + "has_code": false, + "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", - "subsection": "Representing Tables", - "chunk_id": "chapter3_chunks_Representing_Tables_2", + "subsection": "A Simulator for Digital Circuits", "chunk_index": 2, - "content": "Lookup The function lookup then checks to see that the resulting record returned by assoc is not false, undefined , and returns the value (the cdr ) tail ) of the record. make_table1 insert_into_table1 const t = make_table(); insert(\"a\", 10, t); lookup(\"a\", t); lookup1 10 (define (lookup key table) (let ((record (assoc key (cdr table)))) (if record (cdr record) false))) (define (assoc key records) (cond ((null? records) false) ((equal? key (caar records)) (car records)) (else (assoc key (cdr records))))) function lookup(key, table) { const record = assoc(key, tail(table)); return is_undefined(record) ? undefined : tail(record); } function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); }\nTo insert a value in a table under a specified key, we first use assoc to see if there is already a record in the table with this key. If not, we form a new record by cons ing pair ing the key with the value, and insert this at the head of the table s list of records, after the dummy record. If there already is a record with this key, we set the cdr tail of this record to the designated new value.", - "token_count": 290, - "source_files": [ - "subsection3.xml" - ] + "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_2" }, { - "chapter_file": "chapter3_chunks.json", + "content": "For example, we can construct six wires as follows:\n\n```javascript\nmake_wire_usage\n make_wire\n\nconst a = make_wire();\nconst b = make_wire();\nconst c = make_wire();\nconst d = make_wire();\nconst e = make_wire();\nconst s = make_wire();\n```\n\nWe attach a function box to a set of wires by calling a\nfunction\nthat constructs that kind of box.\n\nThe arguments to the constructor\nfunction\nare the wires to be attached to the box.\n\nFor example, given\nthat we can construct and-gates, or-gates, and inverters, we can wire\ntogether the half-adder shown in figure :\n\n```javascript\nor_gate_example\n or_gate\n make_wire_usage\n 'ok'\n\nor_gate(a, b, d);\n```\n\n```javascript\nand_gate_example\n and_gate\n make_wire_usage\n 'ok'\n\nand_gate(a, b, c);\n```\n\n```javascript\ninverter_example\n inverter\n make_wire_usage\n 'ok'\n\ninverter(c, e);\n```\n\n```javascript\nand_gate_example_2\n and_gate\n make_wire_usage\n 'ok'\n\nand_gate(d, e, s);\n```\n\nBetter yet, we can explicitly name this operation by defining a function half_@adder that constructs this circuit, given the four external wires to be attached\n\nto the half-adder:\n\n```javascript\nhalf_adder_example\n half_adder\n\nconst a = make_wire();\nconst b = make_wire();\nconst s = make_wire();\nconst c = make_wire();\nhalf_adder(a, b, s, c);\n```\n\n```javascript\nhalf_adder\n make_wire\n or_gate\n and_gate\n inverter\n half_adder_example\n 'ok'\n\nfunction half_adder(a, b, s, c) {\n const d = make_wire();\n const e = make_wire();\n or_gate(a, b, d);\n and_gate(a, b, c);\n inverter(c, e);\n and_gate(d, e, s);\n return \"ok\";\n}\n```\n\nThe advantage of making this definition is that we can use\nhalf_adder\nitself as a building block in creating more complex\ncircuits.\n\nFigure , for example, shows a\nfull-adder composed of two half-adders and an or-gate.\n\n```javascript\nfull_adder_example\n full_adder\n\nconst a = make_wire();\nconst b = make_wire();\nconst c_in = make_wire();\nconst sum = make_wire();\nconst c_out = make_wire();\nfull_adder(a, b, c_in, sum, c_out);\n```", + "token_count": 282, + "has_code": true, + "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", - "subsection": "Representing Tables", - "chunk_id": "chapter3_chunks_Representing_Tables_3", + "subsection": "A Simulator for Digital Circuits", "chunk_index": 3, - "content": "The header of the table provides us with a fixed location to modify in order to insert the new record. lookup1 insert_into_table1 (define (insert! key value table) (let ((record (assoc key (cdr table)))) (if record (set-cdr! record value) (set-cdr! table (cons (cons key value) (cdr table))))) 'ok) function insert(key, value, table) { const record = assoc(key, tail(table)); if (is_undefined(record)) { set_tail(table, pair(pair(key, value), tail(table))); } else { set_tail(record, value); } return \"ok\"; }", - "token_count": 133, - "source_files": [ - "subsection3.xml" - ] + "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_3" }, { - "chapter_file": "chapter3_chunks.json", + "content": "Figure , for example, shows a\nfull-adder composed of two half-adders and an or-gate.\n\n```javascript\nfull_adder\n make_wire\n half_adder\n or_gate\n full_adder_example\n 'ok'\n\nfunction full_adder(a, b, c_in, sum, c_out) {\n const s = make_wire();\n const c1 = make_wire();\n const c2 = make_wire();\n half_adder(b, c_in, s, c1);\n half_adder(a, s, sum, c2);\n or_gate(c1, c2, c_out);\n return \"ok\";\n}\n```\n\nHaving defined\nfull_adder\nas a\nfunction,\nwe can now use it as a building block for creating still more complex\ncircuits.\n\n(For example, see exercise.)\n\nIn essence, our simulator provides us with the tools to construct a\nlanguage of circuits.\n\nIf we adopt the general perspective on\nlanguages with which we approached the study of\nJavaScript\nin section ,\nwe can say that the primitive function boxes form the primitive\nelements of the language, that wiring boxes together provides a means\nof combination, and that specifying wiring patterns as\nfunctions\nserves as a means of abstraction.\n\nThe primitive function boxes\nforces by which a\nchange in the signal on one wire influences the signals on other\nwires.\n\nTo build function boxes, we use the following operations on\nwires:\n-\n-\nget_signal(wire)\nreturns the current value of the signal on the wire.\n-\n-\nset_signal(wire, new-value):\nchanges the value of the signal on the wire to the new value.\n-\n-\nadd_action(wire, function-of-no-arguments):\nasserts that the designated\nfunction\nshould be run whenever the signal on the wire changes value.\n\nSuch\nfunctions\nare the vehicles by which changes in the signal value on the wire are\ncommunicated to other wires.\n\nIn addition, we will make use of a\nfunction\nafter_delay\nthat takes a time delay and a\nfunction\nto be run and executes the given\nfunction\nafter the given delay.\n\nUsing these\nfunctions,\nwe can define the primitive digital logic functions.", + "token_count": 294, + "has_code": true, + "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", - "subsection": "Representing Tables", - "chunk_id": "chapter3_chunks_Representing_Tables_4", + "subsection": "A Simulator for Digital Circuits", "chunk_index": 4, - "content": "To construct a new table, we simply create a list containing the symbol *table* : just the string \"*table*\" : make_table1 (define (make-table) (list '*table*)) function make_table() { return list(\"*table*\"); }\nIn a two-dimensional table, each value is indexed by two keys. We can construct such a table as a one-dimensional table in which each key identifies a subtable. Figure Figure shows the box-and-pointer diagram for the table math: +: 43 -: 45 *: 42 letters: a: 97 b: 98 \"math\": \"+\": 43 \"-\": 45 \"*\": 42 \"letters\": \"a\": 97 \"b\": 98 which has two subtables. (The subtables don t need a special header symbol, string, since the key that identifies the subtable serves this purpose.) A two-dimensional table. A two-dimensional table.", - "token_count": 189, - "source_files": [ - "subsection3.xml" - ] + "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_4" }, { - "chapter_file": "chapter3_chunks.json", + "content": "Using these\nfunctions,\nwe can define the primitive digital logic functions.\n\nTo connect an input\nto an output through an inverter, we use\nadd_action\nto associate with the input wire a\nfunction\nthat will be run whenever the signal on the input wire changes value.\n\nThe\nfunction\ncomputes the\nlogical_not\nof the input signal, and then, after one\ninverter_delay,\nsets the output signal to be this new value:\n\n```javascript\ninverter\n get_signal\n after_delay\n inverter_example\n 'ok'\n\nfunction inverter(input, output) {\n function invert_input() {\n const new_value = logical_not(get_signal(input));\n after_delay(inverter_delay,\n () => set_signal(output, new_value));\n }\n add_action(input, invert_input);\n return \"ok\";\n}\nfunction logical_not(s) {\n return s === 0\n ? 1\n : s === 1\n ? 0\n : error(s, \"invalid signal\");\n}\n```\n\nAn and-gate is a little more complex.\n\nThe action\nfunction\nmust be run if\neither of the inputs to the gate changes.\n\nIt computes the\n\n```javascript\nlogical_and\n\t(using a function analogous to\n\tlogical_not)\n```\n\nof the values of the signals on the input wires and sets up a change to the new value to occur on the output wire\n\nafter one and_gate_delay.\n\n```javascript\nand_gate\n get_signal\n after_delay\n logical_and\n and_gate_example\n 'ok'\n\nfunction and_gate(a1, a2, output) {\n function and_action_function() {\n const new_value = logical_and(get_signal(a1),\n get_signal(a2));\n after_delay(and_gate_delay,\n () => set_signal(output, new_value));\n }\n add_action(a1, and_action_function);\n add_action(a2, and_action_function);\n return \"ok\";\n}\n```\n\nA wire\nasignal_value\n(initially taken to be 0) and a collection of\naction_functions\nto be run when the signal changes value.\n\nWe implement the wire,\nusing\nfunctions\ntogether with a function\nthat selects the appropriate local operation, just as we did\nwith the simple bank-account object in section\n:", + "token_count": 262, + "has_code": true, + "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", - "subsection": "Representing Tables", - "chunk_id": "chapter3_chunks_Representing_Tables_5", + "subsection": "A Simulator for Digital Circuits", "chunk_index": 5, - "content": "When we look up an item, we use the first key to identify the correct subtable. Then we use the second key to identify the record within the subtable. make_table2 insert_into_table2 const t = list(\"*table*\"); insert(\"a\", \"b\", 10, t); lookup(\"a\", \"b\", t); just_assoc function assoc(key, records) { return is_null(records) ? undefined : equal(key, head(head(records))) ? head(records) : assoc(key, tail(records)); } lookup2 just_assoc 10 (define (lookup key-1 key-2 table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) function lookup(key_1, key_2, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } }\nTo insert a new item under a pair of keys, we use assoc to see if there is a subtable stored under the first key. If not, we build a new subtable containing the single record ( key-2 , ( key_2 , value ) and insert it into the table under the first key. If a subtable already exists for the first key, we insert the new record into this subtable, using the insertion method for one-dimensional tables described above: just_assoc insert_into_table2 (define (insert! key-1 key-2 value table) (let ((subtable (assoc key-1 (cdr table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! table (cons (list key-1 (cons key-2 value)) (cdr table))))) 'ok) function insert(key_1, key_2, value, table) { const subtable = assoc(key_1, tail(table)); if (is_undefined(subtable)) { set_tail(table, pair(list(key_1, pair(key_2, value)), tail(table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } return \"ok\"; }", - "token_count": 556, - "source_files": [ - "subsection3.xml" - ] + "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_5" }, { - "chapter_file": "chapter3_chunks.json", + "content": "We implement the wire,\nusing\nfunctions\ntogether with a function\nthat selects the appropriate local operation, just as we did\nwith the simple bank-account object in section\n:\n\n```javascript\nmake_wire\n call_each\n make_wire_usage\n\nfunction make_wire() {\n let signal_value = 0;\n let action_functions = null;\n function set_my_signal(new_value) {\n if (signal_value !== new_value) {\n signal_value = new_value;\n return call_each(action_functions);\n } else {\n return \"done\";\n }\n }\n function accept_action_function(fun) {\n action_functions = pair(fun, action_functions);\n fun();\n }\n function dispatch(m) {\n return m === \"get_signal\"\n ? signal_value\n : m === \"set_signal\"\n ? set_my_signal\n : m === \"add_action\"\n ? accept_action_function\n : error(m, \"unknown operation -- wire\");\n }\n return dispatch;\n}\n```\n\nThe local\nfunction\nset_my_signal\ntests whether the new signal value changes the signal on the wire.\n\nIf so, it runs each of the action\nfunctions,\nusing the following\nfunction\ncall_each,\nwhich calls each of the items in a list of no-argument\nfunctions:\n\n```javascript\ncall_each\n\nfunction call_each(functions) {\n if (is_null(functions)) {\n return \"done\";\n } else {\n head(functions)();\n return call_each(tail(functions));\n }\n}\n```\n\nThe local\nfunction\naccept_action_function\nadds the given\nfunction\nto the list of\nfunctions\nto be run, and then runs the new\nfunction\nonce.\n\n(See exercise.)\n\nWith the local function set up as specified, we can provide the following functions to access the local operations on wires:\n\n```javascript\nget_signal\n\nfunction get_signal(wire) {\n return wire(\"get_signal\");\n}\nfunction set_signal(wire, new_value) {\n return wire(\"set_signal\")(new_value);\n}\nfunction add_action(wire, action_function) {\n return wire(\"add_action\")(action_function);\n}\n```\n\nWires, which have time-varying signals and may be incrementally attached to\ndevices, are typical of mutable objects.\n\nWe have modeled them as\nfunctions\nwith local state variables that are modified by assignment.\n\nWhen a new\nwire is created, a new set of state variables is allocated (by the\nlet statements in\nmake_wire)\nand a new\nfunction\nis constructed and returned, capturing\nthe environment with the new state variables.", + "token_count": 302, + "has_code": true, + "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", - "subsection": "Representing Tables", - "chunk_id": "chapter3_chunks_Representing_Tables_6", + "subsection": "A Simulator for Digital Circuits", "chunk_index": 6, - "content": "The lookup and insert! insert operations defined above take the table as an argument. This enables us to use programs that access more than one table. Another way to deal with multiple tables is to have separate lookup and insert! insert procedures functions for each table. We can do this by representing a table procedurally, as an object that maintains an internal table as part of its local state. When sent an appropriate message, this table object supplies the procedure function with which to operate on the internal table. Here is a generator for two-dimensional tables represented in this fashion: make_table2 just_assoc (define (make-table) (let ((local-table (list '*table*))) (define (lookup key-1 key-2) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (cdr record) false)) false))) (define (insert! key-1 key-2 value) (let ((subtable (assoc key-1 (cdr local-table)))) (if subtable (let ((record (assoc key-2 (cdr subtable)))) (if record (set-cdr! record value) (set-cdr! subtable (cons (cons key-2 value) (cdr subtable))))) (set-cdr! local-table (cons (list key-1 (cons key-2 value)) (cdr local-table))))) 'ok) (define (dispatch m) (cond ((eq? m 'lookup-proc) lookup) ((eq? m 'insert-proc!) insert!) (else (error \"Unknown operation - - TABLE\" m)))) dispatch)) function make_table() { const local_table = list(\"*table*\"); function lookup(key_1, key_2) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { return undefined; } else { const record = assoc(key_2, tail(subtable)); return is_undefined(record) ? undefined : tail(record); } } function insert(key_1, key_2, value) { const subtable = assoc(key_1, tail(local_table)); if (is_undefined(subtable)) { set_tail(local_table, pair(list(key_1, pair(key_2, value)), tail(local_table))); } else { const record = assoc(key_2, tail(subtable)); if (is_undefined(record)) { set_tail(subtable, pair(pair(key_2, value), tail(subtable))); } else { set_tail(record, value); } } } function dispatch(m) { return m === \"lookup\" ? lookup : m === \"insert\" ? insert : error(m, \"unknown operation -- table\"); } return dispatch; }", - "token_count": 567, - "source_files": [ - "subsection3.xml" - ] + "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_6" }, { - "chapter_file": "chapter3_chunks.json", + "content": "When a new\nwire is created, a new set of state variables is allocated (by the\nlet statements in\nmake_wire)\nand a new\nfunction\nis constructed and returned, capturing\nthe environment with the new state variables.\n\nThe wires are shared among the various devices that have been\nconnected to them.\n\nThus, a change made by an interaction with one\ndevice will affect all the other devices attached to the wire.\n\nThe\nwire communicates the change to its neighbors by calling the action\nfunctions\nprovided to it when the connections were established.\n\nThe only thing needed to complete the simulator is\nafter_delay.\n\nThe idea here is that we maintain a data structure, called an\nagenda , that contains a schedule of things to do.\n\nThe following operations are defined for agendas:\n-\n-\nmake_agenda():\nreturns a new empty agenda.\n-\n-\nis_empty_agenda(agenda)\nis true if the specified agenda is empty.\n-\n-\nfirst_agenda_item(agenda)\nreturns the first item on the agenda.\n-\n-\nremove_first_agenda_item(agenda)\nmodifies the agenda by removing the first item.\n-\n-\nadd_to_agenda(time, action, agenda)\nmodifies the agenda by adding the given action\nfunction\nto be run at the specified time.\n-\n-\ncurrent_time(agenda)\nreturns the current simulation time.\n\nThe particular agenda that we use is denoted by\nthe_agenda.\n\nThe\nfunction\nafter_delay\nadds new elements to\nthe_agenda:\n\n```javascript\nafter_delay\n add_to_agenda\n make_agenda\n the_agenda\n\nfunction after_delay(delay, action) {\n add_to_agenda(delay + current_time(the_agenda),\n action,\n the_agenda);\n}\n```\n\n```javascript\nThe simulation is driven by the function\n\tthe_agenda\n\tin sequence.\n```\n\nIn general, as the simulation runs, new items will be added to the agenda, and\n\n```javascript\npropagate\n remove_first_agenda_item\n first_agenda_item\n the_agenda\n\nfunction propagate() {\n if (is_empty_agenda(the_agenda)) {\n return \"done\";\n } else {\n const first_item = first_agenda_item(the_agenda);\n first_item();\n remove_first_agenda_item(the_agenda);\n return propagate();\n }\n}\n```\n\nThe following\nfunction,\nwhich places a probe on a wire, shows the simulator in\naction.", + "token_count": 302, + "has_code": true, + "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", - "subsection": "Representing Tables", - "chunk_id": "chapter3_chunks_Representing_Tables_7", + "subsection": "A Simulator for Digital Circuits", "chunk_index": 7, - "content": "Using make-table , make_table , we could get and put operations used in section for data-directed programming, as follows: put(\"a\", \"b\", 10); get(\"a\", \"b\"); operation_table make_table2 10 const operation_table = make_table(); const get = operation_table(\"lookup\"); const put = operation_table(\"insert\"); Get The function get takes as arguments two keys, and put takes as arguments two keys and a value. Both operations access the same local table, which is encapsulated within the object created by the call to make-table . make_table .", - "token_count": 120, - "source_files": [ - "subsection3.xml" - ] + "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_7" }, { - "chapter_file": "chapter3_chunks.json", - "section": "Modularity, Objects, and State", - "subsection": "Modeling with Mutable Data", - "chunk_id": "chapter3_chunks_Modeling_with_Mutable_Data_1", - "chunk_index": 1, - "content": "Chapter dealt with compound data as a means for constructing computational objects that have several parts, in order to model real-world objects that have several aspects. In that chapter we introduced the discipline of data abstraction, according to which data structures are specified in terms of constructors, which create data objects, and selectors, which access the parts of compound data objects. But we now know that there is another aspect of data that chapter did not address. The desire to model systems composed of objects that have changing state leads us to the need to modify compound data objects, as well as to construct and select from them. In order to model compound objects with changing state, we will design data abstractions to include, in addition to selectors and constructors, operations called mutators , which modify data objects. For instance, modeling a banking system requires us to change account balances. Thus, a data structure for representing bank accounts might admit an operation (set-balance! $account$ $new$-$value$) set_balance( account , new-value ) that changes the balance of the designated account to the designated new value. Data objects for which mutators are defined are known as mutable data objects . Chapter introduced pairs as a general-purpose glue for synthesizing compound data. We begin this section by defining basic mutators for pairs, so that pairs can serve as building blocks for constructing mutable data objects.", - "token_count": 274, - "source_files": [ - "section3.xml" - ] + "content": "The following\nfunction,\nwhich places a probe on a wire, shows the simulator in\naction.\n\nThe probe tells the wire that, whenever its signal changes value,\nit should print the new signal value, together with the current time and\na name that identifies the\nwire.\n\n```javascript\nprobe\n the_agenda\n get_signal\n\nfunction probe(name, wire) {\n add_action(wire,\n () => display(name + \" \" +\n stringify(current_time(the_agenda)) +\n \", new value = \" +\n stringify(get_signal(wire))));\n}\n\nfunction probe(name, wire) {\n add_action(wire,\n () => name + \" \" +\n stringify(current_time(the_agenda)) +\n \", new value = \" +\n stringify(get_signal(wire)));\n}\n```\n\nWe begin by initializing the agenda and specifying delays for the primitive function boxes:\n\n```javascript\nthe_agenda\n make_agenda\n\nconst the_agenda = make_agenda();\nconst inverter_delay = 2;\nconst and_gate_delay = 3;\nconst or_gate_delay = 5;\n```\n\nNow we define four wires, placing probes on two of them:\n\n```javascript\nprobing_two_wires\n make_wire\n probe\n\nconst input_1 = make_wire();\nconst input_2 = make_wire();\nconst sum = make_wire();\nconst carry = make_wire();\n\nprobe(\"sum\", sum);\n```\n\n```javascript\nprobe_carry\n probing_two_wires\n\nprobe(\"carry\", carry);\n```\n\nNext we connect the wires in a half-adder circuit (as in figure ), set the signal on input_1 to 1, and run the simulation:\n\n```javascript\nhalf_adder_example_2\n half_adder\n probe_carry\n 'ok'\n\nhalf_adder(input_1, input_2, sum, carry);\n```\n\n```javascript\nset_signal_example\n half_adder_example_2\n 'done'\n\nset_signal(input_1, 1);\n```\n\n```javascript\npropagate_example_1\n set_signal_example\n propagate\n 'done'\n\npropagate();\n```\n\nThe input_2 to 1 and allow the values to propagate:\n\n```javascript\nset_signal_example_2\n propagate_example_1\n 'done'\n\nset_signal(input_2, 1);\n```\n\n```javascript\npropagate_example_2\n set_signal_example_2\n 'done'\n\npropagate();\n```\n\nThe\n\nFinally, we give details of the agenda data structure, which holds the functions that are scheduled for future execution.\n\nThe agenda is made up of\ntime segments.\n\nEach time segment is a\npair consisting of a number (the time) and a\n) that holds the\nfunctions\nthat are scheduled to be run during that time segment.\n\n```javascript\nmake_time_segment\n\nfunction make_time_segment(time, queue) {\n return pair(time, queue);\n}\nfunction segment_time(s) { return head(s); }\n\nfunction segment_queue(s) { return tail(s); }\n```", + "token_count": 319, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "A Simulator for Digital Circuits", + "chunk_index": 8, + "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_8" }, { - "chapter_file": "chapter3_chunks.json", - "section": "Modularity, Objects, and State", - "subsection": "Modeling with Mutable Data", - "chunk_id": "chapter3_chunks_Modeling_with_Mutable_Data_2", - "chunk_index": 2, - "content": "These mutators greatly enhance the representational power of pairs, enabling us to build data structures other than the sequences and trees that we worked with in section . We also present some examples of simulations in which complex systems are modeled as collections of objects with local state.", - "token_count": 52, - "source_files": [ - "section3.xml" - ] + "content": "Each time segment is a\npair consisting of a number (the time) and a\n) that holds the\nfunctions\nthat are scheduled to be run during that time segment.\n\nWe will operate on the time-segment queues using the queue operations described in section.\n\nThe agenda itself is a one-dimensional\nin that the segments will be sorted in order of increasing time.\n\nIn\naddition, we store the\ncurrent time (i.e., the time of the last action\nthat was processed) at the head of the agenda.\n\nA newly constructed\nagenda has no time segments and has a current time of 0:\n\n```javascript\nmake_agenda\n\nfunction make_agenda() { return list(0); }\n\nfunction current_time(agenda) { return head(agenda); }\n\nfunction set_current_time(agenda, time) {\n set_head(agenda, time);\n}\nfunction segments(agenda) { return tail(agenda); }\n\nfunction set_segments(agenda, segs) {\n set_tail(agenda, segs);\n}\nfunction first_segment(agenda) { return head(segments(agenda)); }\n\nfunction rest_segments(agenda) { return tail(segments(agenda)); }\n```\n\nAn agenda is empty if it has no time segments:\n\n```javascript\nis_empty_agenda\n make_agenda\n\nfunction is_empty_agenda(agenda) {\n return is_null(segments(agenda));\n}\n```\n\nTo add an action to an agenda, we first check if the agenda is empty.\n\nIf so, we create a time segment for the action and install this in\nthe agenda.\n\nOtherwise, we scan the agenda, examining the time of each\nsegment.\n\nIf we find a segment for our appointed time, we add the\naction to the associated queue.\n\nIf we reach a time later than the one\nto which we are appointed, we insert a new time segment into the\nagenda just before it.\n\nIf we reach the end of the agenda, we must\ncreate a new time segment at the end.", + "token_count": 269, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "A Simulator for Digital Circuits", + "chunk_index": 9, + "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_9" }, { - "chapter_file": "chapter4_chunks.json", - "section": null, - "subsection": "Metalinguistic Abstraction", - "chunk_id": "chapter4_chunks_Metalinguistic_Abstraction_1", + "content": "If we reach the end of the agenda, we must\ncreate a new time segment at the end.\n\n```javascript\nadd_to_agenda\n make_time_segment\n make_queue\n insert_queue\n make_time_segment\n make_agenda\n\nfunction add_to_agenda(time, action, agenda) {\n function belongs_before(segs) {\n return is_null(segs) || time < segment_time(head(segs));\n }\n function make_new_time_segment(time, action) {\n const q = make_queue();\n insert_queue(q, action);\n return make_time_segment(time, q);\n }\n function add_to_segments(segs) {\n if (segment_time(head(segs)) === time) {\n insert_queue(segment_queue(head(segs)), action);\n } else {\n const rest = tail(segs);\n if (belongs_before(rest)) {\n set_tail(segs, pair(make_new_time_segment(time, action),\n tail(segs)));\n } else {\n add_to_segments(rest);\n }\n }\n }\n const segs = segments(agenda);\n if (belongs_before(segs)) {\n set_segments(agenda,\n pair(make_new_time_segment(time, action), segs));\n } else {\n add_to_segments(segs);\n }\n}\n```\n\nThe\nfunction\nthat removes the first item from the agenda deletes the\nitem at the front of the queue in the first time segment.\n\nIf this\ndeletion makes the time segment empty, we remove it from the list of\nsegments:\n\n```javascript\nremove_first_agenda_item\n make_agenda\n is_empty_queue\n delete_queue\n make_time_segment\n\nfunction remove_first_agenda_item(agenda) {\n const q = segment_queue(first_segment(agenda));\n delete_queue(q);\n if (is_empty_queue(q)) {\n set_segments(agenda, rest_segments(agenda));\n } else {}\n}\n```\n\nThe first agenda item is found at the head of the queue in the first\ntime segment.\n\nWhenever we extract an item, we also update the current\ntime:\n\n```javascript\nfirst_agenda_item\n is_empty_agenda\n make_time_segment\n front_queue\n\nfunction first_agenda_item(agenda) {\n if (is_empty_agenda(agenda)) {\n error(\"agenda is empty -- first_agenda_item\");\n } else {\n const first_seg = first_segment(agenda);\n set_current_time(agenda, segment_time(first_seg));\n return front_queue(segment_queue(first_seg));\n }\n}\n```", + "token_count": 225, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "A Simulator for Digital Circuits", + "chunk_index": 10, + "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_10" + }, + { + "content": "The basic operations on\npairspair,\nhead,\nand\ntailcan\nbe used to construct list structure and to select parts\nfrom list structure, but they are incapable of modifying list\nstructure.\n\nThe same is true of the list operations we have used so\nfar, such as\npair,\nhead,\nand\ntail.\n\nTo modify list structures we need new operations.\n\nThe primitive mutators for pairs are\nset_head\nand\nset_tail.\n\nThe function set_head\ntakes two arguments, the first of which must be a pair.\n\nIt modifies this\npair, replacing the\nhead\npointer by a pointer to the second argument of\nset_head.\n\nAs an example, suppose that\nlist(list(\"a\", \"b\"), \"c\", \"d\")\nand\nlist(\"e\", \"f\")\nas illustrated in\nfigure.\n\nEvaluating the expression\nset_head(x, y)\nmodifies the pair to which\nhead\nby the value of\nfigure.\n\nThe structure\n\n```javascript\nis now equivalent to\n\tlist(list(\"e\", \"f\"), \"c\", \"d\").\n```\n\nThe pairs representing the list list(\"a\", \"b\"), identified by the pointer that was replaced, are now detached from the original structure.\n\nCompare figure with figure, which illustrates the result of executing\n\n```javascript\nmutable_list_example\n\nconst x = list(list(\"a\", \"b\"), \"c\");\nconst y = list(\"e\", \"f\");\n```\n\n```javascript\nmutable_list_example\n\nconst z = pair(y, tail(x));\n```\n\nwith\nfigure.\n\nThe\nname\npair\noperation; the list to which\n\nThe\nset_tail\noperation is similar to\nset_head.\n\nThe only difference is that the\ntail\npointer of the pair, rather than the\nhead\npointer, is replaced.\n\nThe effect of executing\nset_tail(x, y)\non the lists of\nfigure\nis shown in\nfigure.\n\nHere the\ntail\npointer of\nlist(\"e\", \"f\").\n\nAlso, the list\nlist(\"c\", \"d\"),\nwhich used to be the\ntail\nof\n\nThe function pair\nbuilds new list structure by creating new pairs,\nwhereas set_@head\nand\nset_tail\nmodify existing pairs.", + "token_count": 280, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Mutable List Structure", "chunk_index": 1, - "content": "In our study of program design, we have seen that expert programmers control the complexity of their designs with the same general techniques used by designers of all complex systems. They combine primitive elements to form compound objects, they abstract compound objects to form higher-level building blocks, and they preserve modularity by adopting appropriate large-scale views of system structure. In illustrating these techniques, we have used Lisp JavaScript as a language for describing processes and for constructing computational data objects and processes to model complex phenomena in the real world. However, as we confront increasingly complex problems, we will find that Lisp, JavaScript, or indeed any fixed programming language, is not sufficient for our needs. We must constantly turn to new languages in order to express our ideas more effectively. Establishing new languages is a powerful strategy for controlling complexity in engineering design; we can often enhance our ability to deal with a complex problem by adopting a new language that enables us to describe (and hence to think about) the problem in a different way, using primitives, means of combination, and means of abstraction that are particularly well suited to the problem at hand. Programming is endowed with a multitude of languages. There are physical languages, such as the procedure definition, function declaration, that are appropriate to the larger-scale organization of systems. Metalinguistic abstraction establishing plays an important role in all branches of engineering design.", - "token_count": 271, - "source_files": [ - "chapter4.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Mutable_List_Structure_1" }, { - "chapter_file": "chapter4_chunks.json", - "section": null, - "subsection": "Metalinguistic Abstraction", - "chunk_id": "chapter4_chunks_Metalinguistic_Abstraction_2", + "content": "The function pair\nbuilds new list structure by creating new pairs,\nwhereas set_@head\nand\nset_tail\nmodify existing pairs.\n\nIndeed, we could\npair\nin terms of the two mutators, together with a\nfunction\nget_new_pair,\nwhich returns a new pair that is not part of any existing list structure.\n\nWe obtain the new pair, set its\nhead\nand\ntail\npointers to the designated objects, and return the new pair as the result of\nthe\npair.\n\n```javascript\nget_new_pair\n\n// The book proposes a primitive function get_new_pair.\n// Since JavaScript does not provide such a function, let's\n// define it as follows, for the sake of the example.\n\nfunction get_new_pair() {\n return pair(undefined, undefined);\n}\n{\n```\n\n```javascript\nget_new_pair\n mutable_pair_example\n [ [ 1, 2 ], 4 ]\n\nfunction pair(x, y) {\n const fresh = get_new_pair();\n set_head(fresh, x);\n set_tail(fresh, y);\n return fresh;\n}\n```\n\n```javascript\nmutable_pair_example\n\npair(pair(1, 2), 4);\n}\n```\n\nWe mentioned in section the\ntheoretical issues of\nsameness and change\nraised by the introduction of assignment.\n\nThese issues arise in practice\nwhen individual pairs are shared among different data objects.\n\nFor example, consider the structure formed by\n\n```javascript\npair_example1\n\nconst x = list(\"a\", \"b\");\nconst z1 = pair(x, x);\n```\n\nAs shown in\nfigure,\nhead\nand\ntail\nboth point to the same pair\nhead\nand\ntail\nof\npair\nis implemented.\n\nIn general, using\npair\nto construct lists will result in an interlinked structure of pairs in\nwhich many individual pairs are shared by many different structures.\n\nThe list pair(x, x).\n\nThe list pair(list(\"a\", \"b\"), list(\"a\", \"b\")).\n\nIn contrast to\n\n```javascript\nfigure,\n\tfigure\n```\n\nshows the structure created by\n\n```javascript\npair_example2\n\nconst z2 = pair(list(\"a\", \"b\"), list(\"a\", \"b\"));\n```\n\nIn this structure, the pairs in the two list(\"a\", \"b\") lists are distinct, although they contain the same strings.\n\nWhen thought of as a list, the same list:", + "token_count": 303, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Mutable List Structure", "chunk_index": 2, - "content": "It is particularly important to computer programming, because in programming not only can we formulate new languages but we can also implement these languages by constructing evaluators. An evaluator (or interpreter ) for a programming language is a procedure function that, when applied to a statement or expression an expression of the language, performs the actions required to evaluate that statement or expression. It is no exaggeration to regard this as the most fundamental idea in programming: The evaluator, which determines the meaning of statements and expressions in a programming language, is just another program. 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. In fact, we can regard almost any program as the evaluator for some language. For instance, the polynomial manipulation system of section embodies the rules of polynomial arithmetic and implements them in terms of operations on list-structured data. If we augment this system with procedures functions to read and print polynomial expressions, we have the core of a special-purpose language for dealing with problems in symbolic mathematics. The digital-logic simulator of section and the constraint propagator of section are legitimate languages in their own right, each with its own primitives, means of combination, and means of abstraction. Seen from this perspective, the technology for coping with large-scale computer systems merges with the technology for building new computer languages, and\nWe now embark on a tour of the technology by which languages are established in terms of other languages.", - "token_count": 300, - "source_files": [ - "chapter4.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Mutable_List_Structure_2" }, { - "chapter_file": "chapter4_chunks.json", - "section": null, - "subsection": "Metalinguistic Abstraction", - "chunk_id": "chapter4_chunks_Metalinguistic_Abstraction_3", + "content": "When thought of as a list, the same list:\n\n```javascript\nabab\n [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ]\n\nlist(list(\"a\", \"b\"), \"a\", \"b\")\n\nlist(list(\"a\", \"b\"), \"a\", \"b\");\n```\n\nIn general, sharing is completely undetectable if we operate on lists using\nonly\npair,\nhead,\nand\ntail.\n\nHowever, if we allow mutators on list structure, sharing becomes\nsignificant.\n\nAs an example of the difference that sharing can make,\nconsider the following\nfunction,\nwhich modifies the\nhead\nof the structure to which it is applied:\n\n```javascript\nset_to_wow\n\nfunction set_to_wow(x) {\n set_head(head(x), \"wow\");\n return x;\n}\n```\n\nEven though the same structure,\napplying\nset_to_wow\nto them yields different results.\n\nWith\nhead\nalso changes the\ntail,\nbecause in\nhead\nand the\ntail\nare the same pair.\n\nWith\nhead\nand\ntail\nare distinct, so\nset_to_wow\nmodifies only the\nhead:\n\n```javascript\nset_to_wow_example_1\n pair_example1\n [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ]\n\nz1;\n```\n\n```javascript\nset_to_wow_example_2\n set_to_wow\n pair_example1\n [ [ 'wow', [ 'b', null ] ], [ 'wow', [ 'b', null ] ] ]\n\nset_to_wow(z1);\n```\n\n```javascript\nset_to_wow_example_3\n pair_example2\n [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ]\n\nz2;\n```\n\n```javascript\nset_to_wow_example_4\n set_to_wow\n pair_example2\n [ [ 'wow', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ]\n\nset_to_wow(z2);\n```\n\n```javascript\nOne way to detect sharing in list structures is to use the\n\t===,\n\twhich we introduced in\n\tsection to test whether two numbers\n\tare equal\n\tand extended in section to test whether\n\ttwo strings are equal. When applied to two nonprimitive values,\n\tx === y\n\ttests whether\n```\n\nThus, with\n\n```javascript\nfigure\n\tand,\n```\n\n```javascript\nhead(z1) === tail(z1)\n```\n\nis true and\n\n```javascript\nhead(z2) === tail(z2)\n```\n\nis false.", + "token_count": 297, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Mutable List Structure", "chunk_index": 3, - "content": "In this chapter we shall use Lisp JavaScript as a base, implementing evaluators as Lisp JavaScript procedures. functions. We will take the first step in understanding how languages are implemented by building an evaluator for Lisp JavaScript itself. The language implemented by our evaluator will be a subset of the Scheme dialect of Lisp that we use in this book. JavaScript. Although the evaluator described in this chapter is written for a particular dialect of Lisp, subset of JavaScript, it contains the essential structure of an evaluator for any expression-oriented language designed for writing programs for a sequential machine. (In fact, most language processors contain, deep within them, a little Lisp evaluator.) The evaluator has been simplified for the purposes of illustration and discussion, and some features have been left out that would be important to include in a production-quality Lisp JavaScript system. Nevertheless, this simple evaluator is adequate to execute most of the programs in this book. An important advantage of making the evaluator accessible as a Lisp JavaScript program is that we can implement alternative evaluation rules by describing these as modifications to the evaluator program. One place where we can use this power to good effect is to gain extra control over the ways in which computational models embody the notion of time, which was so central to the discussion in chapter . There, we mitigated some of the complexities of state and assignment by using streams to decouple the representation of time in the world from time in the computer.", - "token_count": 281, - "source_files": [ - "chapter4.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Mutable_List_Structure_3" }, { - "chapter_file": "chapter4_chunks.json", - "section": null, - "subsection": "Metalinguistic Abstraction", - "chunk_id": "chapter4_chunks_Metalinguistic_Abstraction_4", + "content": "is false.\n\nAs will be seen in the following sections, we can exploit sharing to\ngreatly extend the repertoire of data structures that can be\nrepresented by pairs.\n\nOn the other hand, sharing can also be\nset_head\nand\nset_tail\nshould be used with care; unless we have a good understanding of how our\ndata objects are shared, mutation can have unanticipated\nresults.\n\nWhen we introduced compound data, we observed in section that pairs can be represented purely in terms of functions:\n\n```javascript\ncons_1_2_run\n 1\n\nfunction pair(x, y) {\n function dispatch(m) {\n return m === \"head\"\n ? x\n : m === \"tail\"\n ? y\n : error(m, \"undefined operation -- pair\");\n }\n return dispatch;\n}\n\nfunction head(z) { return z(\"head\"); }\n\nfunction tail(z) { return z(\"tail\"); }\n```\n\nThe same observation is true for mutable data.\n\nWe can implement\nmutable data objects as\nfunctions\nusing assignment and local state.\n\nFor instance, we can extend the above\npair implementation to handle\nset_head\nand\nset_tail\nin a manner analogous to the way we implemented bank accounts using\nmake_account\nin section :\n\n```javascript\ncons_1_2_run_3\n\nconst x = pair(1, 2);\nset_head(x, 3);\nhead(x);\n```\n\n```javascript\npair2\n cons_1_2_run_3\n 3\n\nfunction pair(x, y) {\n function set_x(v) { x = v; }\n function set_y(v) { y = v; }\n return m => m === \"head\"\n ? x\n : m === \"tail\"\n ? y\n : m === \"set_head\"\n ? set_x\n : m === \"set_tail\"\n ? set_y\n : error(m, \"undefined operation -- pair\");\n}\n\nfunction head(z) { return z(\"head\"); }\n\nfunction tail(z) { return z(\"tail\"); }\n\nfunction set_head(z, new_value) {\n z(\"set_head\")(new_value);\n return z;\n}\nfunction set_tail(z, new_value) {\n z(\"set_tail\")(new_value);\n return z;\n}\n```\n\nAssignment is all that is needed, theoretically, to account for the\nbehavior of mutable data.", + "token_count": 287, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Mutable List Structure", "chunk_index": 4, - "content": "Our stream programs, however, were sometimes cumbersome, because they were constrained by the applicative-order evaluation of Scheme. JavaScript. In section , we ll change the underlying language to provide for a more elegant approach, by modifying the evaluator to provide for normal-order evaluation . Section implements a more ambitious linguistic change, whereby statements and expressions have many values, rather than just a single value. In this language of nondeterministic computing , it is natural to express processes that generate all possible values for statements and expressions and then search for those values that satisfy certain constraints. In terms of models of computation and time, this is like having time branch into a set of possible futures and then searching for appropriate time lines. With our nondeterministic evaluator, keeping track of multiple values and performing searches are handled automatically by the underlying mechanism of the language. In section we implement a logic-programming language in which knowledge is expressed in terms of relations, rather than in terms of computations with inputs and outputs. Even though this makes the language drastically different from Lisp, JavaScript, or indeed from any conventional language, we will see that the logic-programming evaluator shares the essential structure of the Lisp JavaScript evaluator.", - "token_count": 235, - "source_files": [ - "chapter4.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Mutable_List_Structure_4" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_1", + "content": "Assignment is all that is needed, theoretically, to account for the\nbehavior of mutable data.\n\nAs soon as we admit\nassignment\nto our language, we raise all the issues, not only of assignment, but of\nmutable data in general.", + "token_count": 39, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Mutable List Structure", + "chunk_index": 5, + "chunk_id": "Modularity_Objects_and_State_Mutable_List_Structure_5" + }, + { + "content": "Computer programs are traditionally organized as\none-directional computations, which perform operations on prespecified\narguments to produce desired outputs.\n\nOn the other hand, we often\nmodel systems in terms of relations among quantities.\n\nFor example, a\nmathematical model of a mechanical structure might include the\ninformation that the deflection $d$ of a metal\nrod is related to the force $F$ on the rod, the\nlength $L$ of the rod, the cross-sectional\narea $A$ , and the elastic modulus\n$E$ via the equation\n\\[\n\\begin{array}{lll}\nd A E & = & F L\n\\end{array}\n\\]\nSuch an equation is not one-directional.\n\nGiven any four of the\nquantities, we can use it to compute the fifth.\n\nYet translating the\nequation into a traditional computer language would force us to choose\none of the quantities to be computed in terms of the other four.\n\nThus, a\nfunction\nfor computing the area $A$ could not be used to\ncompute the deflection $d$ , even though the\ncomputations of $A$ and\n$d$ arise from the same\nequation.\n\nIn this section, we sketch the design of a language that enables us to work\nin terms of\nprimitive constraints , which state that certain relations hold\nbetween quantities.\n\nFor example,\nadder(a, b, c)\nspecifies that the quantities $a$ ,\n$b$ , and $c$ must be\nrelated by the equation $a+b=c$ ,\nmultiplier(x, y, z)\nexpresses the constraint $xy = z$ , and\nconstant(3.14, x)\nsays that the value of $x$ must be 3.14.\n\nOur language provides a means of combining primitive constraints in order to\nexpress more complex relations.\n\nWe combine constraints by constructing\nconstraint networks , in which constraints are joined by\nconnectors.\n\nA connector is an object that holds a\nvalue that may participate in one or more constraints.", + "token_count": 293, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Propagation of Constraints", "chunk_index": 1, - "content": "Section described how the query system works. Now we fill in the details by presenting a complete implementation of the system.", - "token_count": 23, - "source_files": [ - "subsection4.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Propagation_of_Constraints_1" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_2", + "content": "A connector is an object that holds a\nvalue that may participate in one or more constraints.\n\nFor example, we know\nthat the relationship between Fahrenheit and Celsius temperatures is\n\\[\n\\begin{array}{lll}\n9C & = & 5(F - 32)\n\\end{array}\n\\]\nSuch a constraint can be thought of as a network consisting of primitive\nadder, multiplier, and constant constraints\n(figure ).\n\nIn the figure, we see on the\nleft a multiplier box with three terminals, labeled\n$m_1$ , $m_2$ , and\n$p$.\n\nThese connect the multiplier to the rest of\nthe network as follows:\nThe $m_1$ terminal is linked to a connector\n$C$ , which will hold the Celsius temperature.\n\nThe $m_2$ terminal is linked to a connector\n$w$ , which is also linked to a constant box that\nholds 9.\n\nThe $p$ terminal, which the multiplier\nbox constrains to be the product of $m_1$ and\n$m_2$ , is linked to the\n$p$ terminal of another multiplier box, whose\n$m_2$ is connected to a constant 5 and whose\n$m_1$ is connected to one of the terms in a sum.\n\nThe relation $9C = 5(F - 32)$\nexpressed as a constraint network.\n\nComputation by such a network proceeds as follows: When a connector is\ngiven a value (by the user or by a constraint box to which it is\nlinked), it awakens all of its associated constraints (except for the\nconstraint that just awakened it) to inform them that it has a value.\n\nEach awakened constraint box then polls its connectors to see if there\nis enough information to determine a value for a connector.\n\nIf so,\nthe box sets that connector, which then awakens all of its associated\nconstraints, and so on.", + "token_count": 282, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Propagation of Constraints", "chunk_index": 2, - "content": "The the evaluator qeval evaluate_query together with an initial frame stream consisting of a single empty frame. The result of the evaluation is a stream of frames generated by satisfying the query with variable values found in the data base. These frames are used to form a new stream consisting of copies of the original query in which the variables are instantiated with values supplied by the stream of frames, and this final stream is printed at the terminal: displayed: lp_header // functions from SICP JS 4.4.4 query_driver_loop functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 lp_header is_assertion instantiate evaluate_query singleton_stream add_rule_or_assertion put_and disjoin negate javascript_predicate display_stream always_true is_variable_2 is_variable_4 convert_to_query_syntax unparse user_read (define input-prompt \";;; Query input:\") (define output-prompt \";;; Query results:\") (define (query-driver-loop) (prompt-for-input input-prompt) (let ((q (query-syntax-process (read)))) (cond ((assertion-to-be-added? q) (add-rule-or-assertion! (add-assertion-body q)) (newline) (display \"Assertion added to data base.\") (query-driver-loop)) (else (newline) (display output-prompt) (display-stream (stream-map (lambda (frame) (instantiate q frame (lambda (v f) (contract-question-mark v)))) (qeval q (singleton-stream '())))) (query-driver-loop))))) const input_prompt = \"Query input:\"; const output_prompt = \"Query results:\"; function query_driver_loop() { const input = user_read(input_prompt) + \";\"; if (is_null(input)) { display(\"evaluator terminated\"); } else { const expression = parse(input); const query = convert_to_query_syntax(expression); if (is_assertion(query)) { add_rule_or_assertion(assertion_body(query)); display(\"Assertion added to data base.\"); } else { display(output_prompt); display_stream( stream_map( frame => unparse(instantiate_expression(expression, frame)), evaluate_query(query, singleton_stream(null)))); } return query_driver_loop(); } } const input_prompt = \"Query input:\"; function query_driver_loop() { const input = user_read(input_prompt); if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } return query_driver_loop(); } } append_to_form query_driver_loop(); // enter: append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\")) parse_query_verbose('assert(son(\"Adam\", \"Cain\"))'); parse_query_verbose('son(\"Adam\", x)'); process_query query_driver_loop function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); display(\"---- driver loop input -----\"); display(unparse(exp)); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); display(\"Assertion added to data base.\"); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } function process_query(input) { if (is_null(input)) { display(\"--- evaluator terminated ---\"); } else { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); if (is_assertion(q)) { add_rule_or_assertion(assertion_body(q)); } else { display(\"------ query results -------\", \"\"); display_stream( stream_map( frame => unparse(instantiate_expression(exp, frame)), evaluate_query(q, singleton_stream(null)))); } } } function first_answer(input) { const exp = parse(input + \";\"); const q = convert_to_query_syntax(exp); const frames = evaluate_query(q, singleton_stream(null)); return is_null(frames) ? \"no matching data\" : unparse(instantiate_expression(exp, head(frames))); } Here, as in the other evaluators in this chapter, we use an assertion-to-be-added? and the selector add-assertion-body , is given in section . Add-rule-or-assertion! is defined in section . Here, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation. (We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax , which is declared in section along with the predicate is_assertion and the selector assertion_body . The function add_rule_or_assertion is declared in section . The frames resulting from query evaluation are used to instantiate the syntax representation, and the result is unparsed into a string for display. The functions instantiate_expression and unparse are declared in section .", - "token_count": 1175, - "source_files": [ - "subsection4.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Propagation_of_Constraints_2" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_3", + "content": "If so,\nthe box sets that connector, which then awakens all of its associated\nconstraints, and so on.\n\nFor instance, in conversion between\nCelsius and Fahrenheit, $w$ ,\n$x$ , and $y$ are\nimmediately set by the constant boxes to $9$, $5$, and $32$, respectively.\n\nThe\nconnectors awaken the multipliers and the adder, which determine that there\nis not enough information to proceed.\n\nIf the user (or some other part of\nthe network) sets $C$ to a value (say 25), the\nleftmost multiplier will be awakened, and it will set\n$u$ to $25\\cdot 9=225$.\n\nThen $u$ awakens the second multiplier, which sets\n$v$ to $45$, and $v$\nawakens the adder, which sets $F$ to $77$.\n\n```javascript\nTo use the constraint system to carry out the temperature computation\n\toutlined above, we first call the constructor\n\tmake_connector\n\tto create two connectors,\n```\n\n```javascript\ncelsius_fahrenheit_converter_example\n celsius_fahrenheit_converter\n 'ok'\n\nconst C = make_connector();\nconst F = make_connector();\ncelsius_fahrenheit_converter(C, F);\n```\n\nThe function that creates the network is defined as follows:\n\n```javascript\ncelsius_fahrenheit_converter\n make_connector\n multiplier_2\n adder\n constant\n celsius_fahrenheit_converter_example\n 'ok'\n\nfunction celsius_fahrenheit_converter(c, f) {\n const u = make_connector();\n const v = make_connector();\n const w = make_connector();\n const x = make_connector();\n const y = make_connector();\n multiplier(c, w, u);\n multiplier(v, x, u);\n adder(v, y, f);\n constant(9, w);\n constant(5, x);\n constant(32, y);\n return \"ok\";\n}\n```\n\nThis function creates the internal connectors using the primitive constraint constructors , expressing these combinations of primitive elements in terms of functions automatically provides our\n\nlanguage with a means of abstraction for compound objects.\n\nTo watch the network in action, we can place probes on the connectors\nfunction\nsimilar to the one we used to monitor wires in\nsection.\n\nPlacing a probe on a\nconnector will cause a message to be printed whenever the connector is\ngiven a value:\n\n```javascript\ncelsius_probe\n probe_2\n celsius_fahrenheit_converter_example\n\nprobe(\"Celsius temp\", C);\nprobe(\"Fahrenheit temp\", F);\n```", + "token_count": 306, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Propagation of Constraints", "chunk_index": 3, - "content": "Before doing any processing on an input expression, the driver loop transforms it syntactically into a form that makes the processing more efficient. This involves changing the query-syntax-process and contract-question-mark (section ). To ?x in exp is bound to ?y as the result of unification and ?y is in turn bound to 5). The action to take if a variable cannot be instantiated is given by a procedural argument to instantiate . instantiate make_binding variable express (define (instantiate exp frame unbound-var-handler) (define (copy exp) (cond ((var? exp) (let ((binding (binding-in-frame exp frame))) (if binding (copy (binding-value binding)) (unbound-var-handler exp frame)))) ((pair? exp) (cons (copy (car exp)) (copy (cdr exp)))) (else exp))) (copy exp)) The procedures that manipulate bindings are defined in section . The qeval evaluate_query procedure, function, called by the query-driver-loop , query_driver_loop , is the basic evaluator of the query system. It takes as inputs a query and a stream of frames, and it returns a stream of extended frames. It identifies special syntactic forms by a get and put , just as we did in implementing generic operations in chapter . Any query that is not identified as a special syntactic form is assumed to be a simple query, to be processed by simple-query . simple_query .", - "token_count": 287, - "source_files": [ - "subsection4.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Propagation_of_Constraints_3" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_4", + "content": "Placing a probe on a\nconnector will cause a message to be printed whenever the connector is\ngiven a value:\n\nNext we set the value of set_value tells\n\n```javascript\nset_value_example\n has_value\n celsius_probe\n 'done'\n\nset_value(C, 25, \"user\");\n```\n\nThe probe on\n\nNow we can try to set\n\n```javascript\nset_value_example_2\n set_value_example\n\nset_value(F, 212, \"user\");\n```\n\nThe connector complains that it has sensed a contradiction: Its value\nis 77, and someone is trying to set it to 212.\n\nIf we really want to\nreuse the network with new values, we can tell\n\n```javascript\nforget_value_example\n set_value_example\n 'done'\n\nforget_value(C, \"user\");\n```\n\n\"user\",\nwho set its value originally, is now retracting that value, so\n77.\n\nThus,\n\nNow that\n\n```javascript\nset_value_example_3\n forget_value_example\n 'done'\n\nset_value(F, 212, \"user\");\n```\n\nThis new value, when propagated through the network, forces\n\nThe constraint system is implemented via procedural objects with local\nstate, in a manner very similar to the digital-circuit simulator of\nsection.\n\nAlthough the primitive\nobjects of the constraint system are somewhat more complex, the overall\nsystem is simpler, since there is no concern about agendas and logic delays.\n\nThe basic\n-\n-\nhas_value(connector)\ntells whether the connector has a value.\n-\n-\nget_value(connector)\nreturns the connector s current value.\n-\n-\nset_value(connector, new-value, informant)\nindicates that the informant is requesting the connector to set its\nvalue to the new value.\n-\n-\nforget_value(connector, retractor)\ntells the connector that the retractor is requesting it to forget its\nvalue.\n-\n-\nconnect(connector, new-constraint)\ntells the connector to participate in the new constraint.\n\nThe connectors communicate with the constraints by means of the\nfunctions\ninform_@about_@value,\nwhich tells the given constraint that the connector has a value, and\ninform_@about_@no_@value,\nwhich tells the constraint that the connector has lost its value.\n\nfunction with local state (the function", + "token_count": 292, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Propagation of Constraints", "chunk_index": 4, - "content": "evaluate_query operation_table_from_chapter_3 operation_table simple_query type (define (qeval query frame-stream) (let ((qproc (get (type query) 'qeval))) (if qproc (qproc (contents query) frame-stream) (simple-query query frame-stream)))) function evaluate_query(query, frame_stream) { const qfun = get(type(query), \"evaluate_query\"); return is_undefined(qfun) ? simple_query(query, frame_stream) : qfun(contents(query), frame_stream); } Type The functions type and contents , defined in section , implement the abstract syntax of the special forms. the abstract syntax of the syntactic forms. The simple-query simple_query procedure function handles simple queries. It takes as arguments a simple query (a pattern) together with a stream of frames, and it returns the stream formed by extending each frame by all data-base matches of the query.", - "token_count": 183, - "source_files": [ - "subsection4.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Propagation_of_Constraints_4" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_5", + "content": "function with local state (the function\n\n```javascript\nadder\n has_value\n\nfunction adder(a1, a2, sum) {\n function process_new_value() {\n if (has_value(a1) && has_value(a2)) {\n set_value(sum, get_value(a1) + get_value(a2), me);\n } else if (has_value(a1) && has_value(sum)) {\n set_value(a2, get_value(sum) - get_value(a1), me);\n } else if (has_value(a2) && has_value(sum)) {\n set_value(a1, get_value(sum) - get_value(a2), me);\n } else {}\n }\n function process_forget_value() {\n forget_value(sum, me);\n forget_value(a1, me);\n forget_value(a2, me);\n process_new_value();\n }\n function me(request) {\n if (request === \"I have a value.\") {\n process_new_value();\n } else if (request === \"I lost my value.\") {\n process_forget_value();\n } else {\n error(request, \"unknown request -- adder\");\n }\n }\n connect(a1, me);\n connect(a2, me);\n connect(sum, me);\n return me;\n}\n```\n\nThe function\nconnects the new adder to the designated\nconnectors and returns it as its value.\n\nThe\nfunction\nfunctions.\n\nThe following\nsyntax interfaces (see\nfootnote in\nsection ) are used in conjunction\nwith the dispatch:\n\n```javascript\ninform_about_value\n\nfunction inform_about_value(constraint) {\n return constraint(\"I have a value.\");\n}\n\nfunction inform_about_no_value(constraint) {\n return constraint(\"I lost my value.\");\n}\n```\n\nThe adder s local\nfunction\nprocess_new_value\nis called when the adder is informed that one of its connectors has a value.\n\nThe adder first checks to see if both\nset_value\nis\nprocess_new_value.\n\nThe reason for this last step is that one or more connectors may still\nhave a value (that is, a connector may have had a value that was not\noriginally set by the adder), and these values may need to be\npropagated back through the adder.\n\nA multiplier is very similar to an adder.\n\nIt will set its", + "token_count": 255, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Propagation of Constraints", "chunk_index": 5, - "content": "simple_query stream_flatmap find_assertions apply_rules (define (simple-query query-pattern frame-stream) (stream-flatmap (lambda (frame) (stream-append-delayed (find-assertions query-pattern frame) (delay (apply-rules query-pattern frame)))) frame-stream)) function simple_query(query_pattern, frame_stream) { return stream_flatmap( frame => stream_append_delayed( find_assertions(query_pattern, frame), () => apply_rules(query_pattern, frame)), frame_stream); }\nFor each frame in the input stream, we use find-assertions find_assertions (section ) to match the pattern against all assertions in the data base, producing a stream of extended frames, and we use apply-rules apply_rules (section ) to apply all possible rules, producing another stream of extended frames. These two streams are combined (using stream-append-delayed , stream_append_delayed , section ) to make a stream of all the ways that the given pattern can be satisfied consistent with the original frame (see exercise ). The streams for the individual input frames are combined using stream-flatmap stream_flatmap (section ) to form one large stream of all the ways that any of the frames in the original input stream can be extended to produce a match with the given pattern. And by the We handle and queries as illustrated in figure with the conjoin procedure. Conjoin function, which takes as inputs the conjuncts and the frame stream and returns the stream of extended frames.", - "token_count": 297, - "source_files": [ - "subsection4.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Propagation_of_Constraints_5" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_6", + "content": "It will set its\n\n```javascript\nmultiplier_2\n has_value\n\nfunction multiplier(m1, m2, product) {\n function process_new_value() {\n if ((has_value(m1) && get_value(m1) === 0)\n || (has_value(m2) && get_value(m2) === 0)) {\n set_value(product, 0, me);\n } else if (has_value(m1) && has_value(m2)) {\n set_value(product, get_value(m1) * get_value(m2), me);\n } else if (has_value(product) && has_value(m1)) {\n set_value(m2, get_value(product) / get_value(m1), me);\n } else if (has_value(product) && has_value(m2)) {\n set_value(m1, get_value(product) / get_value(m2), me);\n } else {}\n }\n function process_forget_value() {\n forget_value(product, me);\n forget_value(m1, me);\n forget_value(m2, me);\n process_new_value();\n }\n function me(request) {\n if (request === \"I have a value.\") {\n process_new_value();\n } else if (request === \"I lost my value.\") {\n process_forget_value();\n } else {\n error(request, \"unknown request -- multiplier\");\n }\n }\n connect(m1, me);\n connect(m2, me);\n connect(product, me);\n return me;\n}\n```\n\nA \"I have a value.\" or \"I lost my value.\" message sent to the constant box will produce an error.\n\n```javascript\nconstant\n has_value\n\nfunction constant(value, connector) {\n function me(request) {\n error(request, \"unknown request -- constant\");\n }\n connect(connector, me);\n set_value(connector, value, me);\n return me;\n}\n```\n\nFinally, a probe prints a message about the setting or unsetting of the designated connector:\n\n```javascript\nredefine_display\n\nconst display = x => x;\n```\n\n```javascript\nprobe_2\n redefine_display\n has_value\n\nfunction probe(name, connector) {\n function print_probe(value) {\n display(\"Probe: \" + name + \" = \" + stringify(value));\n }\n function process_new_value() {\n print_probe(get_value(connector));\n }\n function process_forget_value() {\n print_probe(\"?\");\n }\n function me(request) {\n return request === \"I have a value.\"\n ? process_new_value()\n : request === \"I lost my value.\"\n ? process_forget_value()\n : error(request, \"unknown request -- probe\");\n }\n connect(connector, me);\n return me;\n}\n```\n\nA connector is represented as a procedural object with local state variables s value; and", + "token_count": 275, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Propagation of Constraints", "chunk_index": 6, - "content": "First, conjoin processes the stream of frames to find the stream of all possible frame extensions that satisfy the first query in the conjunction. Then, using this as the new frame stream, it recursively applies conjoin to the rest of the queries. conjoin is_empty_conjunction operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed (define (conjoin conjuncts frame-stream) (if (empty-conjunction? conjuncts) frame-stream (conjoin (rest-conjuncts conjuncts) (qeval (first-conjunct conjuncts) frame-stream)))) function conjoin(conjuncts, frame_stream) { return is_empty_conjunction(conjuncts) ? frame_stream : conjoin(rest_conjuncts(conjuncts), evaluate_query(first_conjunct(conjuncts), frame_stream)); } The expression statement put_and conjoin (put 'and 'qeval conjoin) put(\"and\", \"evaluate_query\", conjoin); sets up qeval evaluate_query to dispatch to conjoin when an and form is encountered. Or We handle or queries similarly, as shown in figure . figure . The output streams for the various disjuncts of the or are computed separately and merged using the interleave-delayed interleave_delayed procedure function from section . (See exercises and .)", - "token_count": 278, - "source_files": [ - "subsection4.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Propagation_of_Constraints_6" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_7", + "content": "A connector is represented as a procedural object with local state variables s value; and\n\n```javascript\nmake_connector\n for_each_except\n inform_about_value\n has_value\n\nfunction make_connector() {\n let value = false;\n let informant = false;\n let constraints = null;\n function set_my_value(newval, setter) {\n if (!has_value(me)) {\n value = newval;\n informant = setter;\n return for_each_except(setter,\n inform_about_value,\n constraints);\n } else if (value !== newval) {\n error(list(value, newval), \"contradiction\");\n } else {\n return \"ignored\";\n }\n }\n function forget_my_value(retractor) {\n if (retractor === informant) {\n informant = false;\n return for_each_except(retractor,\n inform_about_no_value,\n constraints);\n } else {\n return \"ignored\";\n }\n }\n function connect(new_constraint) {\n if (is_null(member(new_constraint, constraints))) {\n constraints = pair(new_constraint, constraints);\n } else {}\n if (has_value(me)) {\n inform_about_value(new_constraint);\n } else {}\n return \"done\";\n }\n function me(request) {\n if (request === \"has_value\") {\n return informant !== false;\n } else if (request === \"value\") {\n return value;\n } else if (request === \"set_value\") {\n return set_my_value;\n } else if (request === \"forget\") {\n return forget_my_value;\n } else if (request === \"connect\") {\n return connect;\n } else {\n error(request, \"unknown operation -- connector\");\n }\n }\n return me;\n}\n```\n\nThe connector s local\nfunction\nset_my_value\nis called when there is a request to set the connector s value.\n\nIf\nthe connector does not currently have a value, it will set its value and\nremember as\nfunction\nto all items in a list except a given one:\n\n```javascript\nfor_each_except\n\nfunction for_each_except(exception, fun, list) {\n function loop(items) {\n if (is_null(items)) {\n return \"done\";\n } else if (head(items) === exception) {\n return loop(tail(items));\n } else {\n fun(head(items));\n return loop(tail(items));\n }\n }\n return loop(list);\n}\n```\n\nIf a connector is asked to forget its value, it runs\n\n```javascript\nforget_my_value,\n\ta local function that\n```\n\nfirst checks to make sure that the request is coming from the same\nobject that set the value originally.", + "token_count": 297, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Propagation of Constraints", "chunk_index": 7, - "content": "disjoin operation_table_from_chapter_3 operation_table is_empty_conjunction stream_append_delayed (define (disjoin disjuncts frame-stream) (if (empty-disjunction? disjuncts) the-empty-stream (interleave-delayed (qeval (first-disjunct disjuncts) frame-stream) (delay (disjoin (rest-disjuncts disjuncts) frame-stream))))) (put 'or 'qeval disjoin) function disjoin(disjuncts, frame_stream) { return is_empty_disjunction(disjuncts) ? null : interleave_delayed( evaluate_query(first_disjunct(disjuncts), frame_stream), () => disjoin(rest_disjuncts(disjuncts), frame_stream)); } put(\"or\", \"evaluate_query\", disjoin);\nThe predicates and selectors for the syntax representation of conjuncts and disjuncts are given in section . Not The not syntactic form is handled by the method outlined in section . We attempt to extend each frame in the input stream to satisfy the query being negated, and we include a given frame in the output stream only if it cannot be extended. negate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream (define (negate operands frame-stream) (stream-flatmap (lambda (frame) (if (stream-null?", - "token_count": 283, - "source_files": [ - "subsection4.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Propagation_of_Constraints_7" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_8", + "content": "first checks to make sure that the request is coming from the same\nobject that set the value originally.\n\nIf so, the connector informs its\nassociated constraints about the loss of the value.\n\nThe local function Then, if the connector has a value, it informs the new constraint of this fact.\n\nThe connector s\nfunction\nfunctions\nand also represents the connector as an object.\n\nThe following\nfunctions\nprovide a syntax interface for the dispatch:\n\n```javascript\nhas_value\n\nfunction has_value(connector) {\n return connector(\"has_value\");\n}\nfunction get_value(connector) {\n return connector(\"value\");\n}\nfunction set_value(connector, new_value, informant) {\n return connector(\"set_value\")(new_value, informant);\n}\nfunction forget_value(connector, retractor) {\n return connector(\"forget\")(retractor);\n}\n\nfunction connect(connector, new_constraint) {\n return connector(\"connect\")(new_constraint);\n}\n```", + "token_count": 112, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Propagation of Constraints", "chunk_index": 8, - "content": "(qeval (negated-query operands) (singleton-stream frame))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'not 'qeval negate) function negate(exps, frame_stream) { return stream_flatmap( frame => is_null(evaluate_query(negated_query(exps), singleton_stream(frame))) ? singleton_stream(frame) : null, frame_stream); } put(\"not\", \"evaluate_query\", negate);\nLisp-value The javascript_predicate syntactic form is a filter similar to not . Each frame in the stream is used to instantiate the variables in the pattern, the indicated predicate is applied, and the frames for which the predicate returns false are filtered out of the input stream. An error results if there are unbound pattern variables. Each frame in the stream is used to instantiate the variables in the predicate, the instantiated predicate is evaluated, and the frames for which the predicate evaluates to false are filtered out of the input stream. The instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation.", - "token_count": 225, - "source_files": [ - "subsection4.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Propagation_of_Constraints_8" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_9", - "chunk_index": 9, - "content": "compound_queries_4 process_query first_answer('and(salary(person, amount), javascript_predicate(amount > 50000))'); // parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\"); javascript_predicate operation_table_from_chapter_3 operation_table stream_flatmap singleton_stream (define (lisp-value call frame-stream) (stream-flatmap (lambda (frame) (if (execute (instantiate call frame (lambda (v f) (error \"Unknown pat var - - LISP-VALUE\" v)))) (singleton-stream frame) the-empty-stream)) frame-stream)) (put 'lisp-value 'qeval lisp-value) function javascript_predicate(exps, frame_stream) { return stream_flatmap( frame => evaluate(instantiate_expression( javascript_predicate_expression(exps), frame), the_global_environment) ? singleton_stream(frame) : null, frame_stream); } put(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);\nExecute , which applies the predicate to the arguments, must eval the predicate expression to get the procedure to apply. However, it must not evaluate the arguments, since they are already the actual arguments, not expressions whose evaluation (in Lisp) will produce the arguments. Note that execute is implemented using eval and apply from the underlying Lisp system.", - "token_count": 265, - "source_files": [ - "subsection4.xml" - ] + "content": "The mutators\nset_head\nand\nset_tail\nenable us to use pairs to construct data structures that cannot be built\nwith\npair,\nhead,\nand\ntail\nalone.\n\nThis section shows how to use pairs to represent a data structure\ncalled a queue.\n\nSection will show how to\nrepresent data structures called tables.\n\nA queue is a sequence in which items are inserted at one end\n(called the\nrear of the queue) and deleted from the other end (the\nfront ).\n\nFigure\nshows an initially empty queue in which the items\nFIFO (first in, first out) buffer.\n\n```javascript\nOperation\n\n\t\tResulting Queue\n\n\t\tconst q = make_queue();\n\n\t\tinsert_queue(q, \"a\");\n\n\t\ta\n\n\t\tinsert_queue(q, \"b\");\n\n\t\ta b\n\n\t\tdelete_queue(q);\n\n\t\tb\n\n\t\tinsert_queue(q, \"c\");\n\n\t\tb c\n\n\t\tinsert_queue(q, \"d\");\n\n\t\tb c d\n\n\t\tdelete_queue(q);\n\n\t\tc d\n\n Queue operations.\n```\n\nIn terms of - - a constructor: make_queue() returns an empty queue (a queue containing no items). \\vspace{6pt} - - a predicate: is_empty_queue(queue) tests if\n\nthe queue is empty. \\vspace{6pt} - - a selector: front_queue(queue) returns the object at the front of the queue, signaling an error if the queue\n\nis empty; it does not modify the queue. \\vspace{6pt} - - two mutators: insert_queue(queue, item) inserts \\\\[4pt] delete_queue(queue) removes\n\nBecause a queue is a sequence of items, we could certainly represent\nit as an ordinary list; the front of the queue would be the\nhead\nof the list, inserting an item in the queue would amount to appending\na new element at the end of the list, and deleting an item from the\nqueue would just be taking the\ntail\nof the list.\n\nHowever, this representation is inefficient, because in order\nto insert an item we must scan the list until we reach the end.", + "token_count": 280, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Representing Queues", + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_Representing_Queues_1" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_10", - "chunk_index": 10, - "content": "execute functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 is_empty_conjunction (define (execute exp) (apply (eval (predicate exp) user-initial-environment) (args exp)))\nThe always-true special form always_true syntactic form provides for a query that is always satisfied. It ignores its contents (normally empty) and simply passes through all the frames in the input stream. Always-true is used by the rule-body selector (section ) The rule_body selector (section ) uses always_true always_true operation_table_from_chapter_3 operation_table (define (always-true ignore frame-stream) frame-stream) (put 'always-true 'qeval always-true) function always_true(ignore, frame_stream) { return frame_stream; } put(\"always_true\", \"evaluate_query\", always_true); The selectors that define the syntax of not and lisp-value javascript_predicate are given in section . Find-assertions , The function find_assertions , simple-query simple_query (section ), takes as input a pattern and a frame. It returns a stream of frames, each extending the given one by a data-base match of the given pattern. It uses fetch-assertions fetch_assertions (section ) to get a stream of all the assertions in the data base that should be checked for a match against the pattern and the frame.", - "token_count": 290, - "source_files": [ - "subsection4.xml" - ] + "content": "However, this representation is inefficient, because in order\nto insert an item we must scan the list until we reach the end.\n\nSince the\nonly method we have for scanning a list is by successive\ntail\noperations, this scanning requires $\\Theta(n)$\nsteps for a list of $n$ items.\n\nA simple\nmodification to the list representation overcomes this disadvantage by\nallowing the queue operations to be implemented so that they require\n$\\Theta(1)$ steps; that is, so that the number\nof steps needed is independent of the length of the queue.\n\nThe difficulty with the list representation arises from the need to\nscan to find the end of the list.\n\nThe reason we need to scan is that,\nalthough the standard way of representing a list as a chain of pairs\nreadily provides us with a pointer to the beginning of the list, it\ngives us no easily accessible pointer to the end.\n\nThe modification\nthat avoids the drawback is to represent the queue as a list, together\nwith an additional pointer that indicates the final pair in the list.\n\nThat way, when we go to insert an item, we can consult the rear\npointer and so avoid scanning the list.\n\nA queue is represented, then, as a pair of pointers,\nfront_ptr\nand\nrear_ptr,\nwhich indicate, respectively, the first and last pairs in an ordinary list.\n\nSince we would like the queue to be an identifiable object, we can use\npair\nto combine the two pointers.\n\nThus, the queue itself will be the\npair\nof the two pointers.\n\nFigure\nillustrates this representation.\n\nImplementation of a queue as a list with front and rear pointers.\n\nTo define the queue operations we use the following functions, which enable us to select and to modify the front and rear pointers of a\n\nqueue:", + "token_count": 298, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Representing Queues", + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_Representing_Queues_2" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_11", - "chunk_index": 11, - "content": "The reason for fetch-assertions fetch_@assertions here is that we can often apply simple tests that will eliminate many of the entries in the data base from the pool of candidates for a successful match. The system would still work if we eliminated fetch-assertions fetch_assertions and simply checked a stream of all assertions in the data base, but the computation would be less efficient because we would need to make many more calls to the matcher. find_assertions stream_flatmap check_an_assertion fetch_assertions (define (find-assertions pattern frame) (stream-flatmap (lambda (datum) (check-an-assertion datum pattern frame)) (fetch-assertions pattern frame))) function find_assertions(pattern, frame) { return stream_flatmap( datum => check_an_assertion(datum, pattern, frame), fetch_assertions(pattern, frame)); }\nCheck-an-assertion The function check_an_assertion takes as arguments a data object (assertion), (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended frame or the-empty-stream null if the match fails. check_an_assertion pattern_match singleton_stream (define (check-an-assertion assertion query-pat query-frame) (let ((match-result (pattern-match query-pat assertion query-frame))) (if (eq?", - "token_count": 272, - "source_files": [ - "subsection4.xml" - ] + "content": "queue:\n\n```javascript\nmodify_pointers_example\n\nconst q = pair(1, 2);\nset_front_ptr(q, 42);\nfront_ptr(q);\n```\n\n```javascript\nmodify_pointers\n modify_pointers_example\n 42\n\nfunction front_ptr(queue) { return head(queue); }\n\nfunction rear_ptr(queue) { return tail(queue); }\n\nfunction set_front_ptr(queue, item) { set_head(queue, item); }\n\nfunction set_rear_ptr(queue, item) { set_tail(queue, item); }\n```\n\nNow we can implement the actual queue operations.\n\nWe will consider a\nqueue to be empty if its front pointer is the empty list:\n\n```javascript\nis_empty_queue_example\n\nconst q = pair(null, 2);\nis_empty_queue(q);\n```\n\n```javascript\nmodify_pointers\n is_empty_queue\n is_empty_queue_example\n true\n\nfunction is_empty_queue(queue) { return is_null(front_ptr(queue)); }\n```\n\nThe make_queue constructor returns, as an initially empty queue, a pair whose head and tail are both the empty list:\n\n```javascript\nmake_queue_example\n modify_pointers\n\nconst q = make_queue();\nfront_ptr(q);\n```\n\n```javascript\nmake_queue\n make_queue_example\n null\n\nfunction make_queue() { return pair(null, null); }\n```\n\nTo select the item at the front of the queue, we return the head of the pair indicated by the front pointer:\n\n```javascript\nfront_queue_example\n\nconst q = pair(pair(1, 2), 3);\nfront_queue(q);\n```\n\n```javascript\nfront_queue\n is_empty_queue\n front_queue_example\n 1\n\nfunction front_queue(queue) {\n return is_empty_queue(queue)\n ? error(queue, \"front_queue called with an empty queue\")\n : head(front_ptr(queue));\n}\n```\n\nTo insert an item in a queue, we follow the method whose result is\nindicated in\nfigure.\n\nWe first create a new\npair whose\nhead\nis the item to be inserted and whose\ntail\nis the empty list.\n\nIf the queue was initially empty, we set the front and\nrear pointers of the queue to this new pair.\n\nOtherwise, we modify the\nfinal pair in the queue to point to the new pair, and also set the\nrear pointer to the new pair.\n\n```javascript\nResult of using\n\t insert_queue(q, \"d\") on the\n\t queue of figure.\n```\n\n```javascript\nprint_queue_example\n make_queue\n insert_queue\n delete_queue\n\nconst q1 = make_queue();\ninsert_queue(q1, \"a\");\ninsert_queue(q1, \"b\");\ndelete_queue(q1);\ndelete_queue(q1);\n```", + "token_count": 294, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Representing Queues", + "chunk_index": 3, + "chunk_id": "Modularity_Objects_and_State_Representing_Queues_3" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_12", - "chunk_index": 12, - "content": "match-result 'failed) the-empty-stream (singleton-stream match-result)))) function check_an_assertion(assertion, query_pat, query_frame) { const match_result = pattern_match(query_pat, assertion, query_frame); return match_result === \"failed\" ? null : singleton_stream(match_result); } The basic pattern matcher returns either the symbol failed string \"failed\" or an extension of the given frame. The basic idea of the matcher is to check the pattern against the data, element by element, accumulating bindings for the pattern variables. If the pattern and the data object are the same, the match succeeds and we return the frame of bindings accumulated so far. Otherwise, if the pattern is a variable (checked by the function is_variable declared in section ) we extend the current frame by binding the variable to the data, so long as this is consistent with the bindings already in the frame. If the pattern and the data are both pairs, we (recursively) match the car head of the pattern against the car head of the data to produce a frame; in this frame we then match the cdr tail of the pattern against the cdr tail of the data. If none of these cases are applicable, the match fails and we return the symbol failed . string \"failed\" . pattern_match extend_if_consistent variable (define (pattern-match pat dat frame) (cond ((eq? frame 'failed) 'failed) ((equal? pat dat) frame) ((var?", - "token_count": 294, - "source_files": [ - "subsection4.xml" - ] + "content": "Otherwise, we modify the\nfinal pair in the queue to point to the new pair, and also set the\nrear pointer to the new pair.\n\n```javascript\ninsert_queue\n modify_pointers\n is_empty_queue\n print_queue_example\n [ null, [ 'b', null ] ]\n\nfunction insert_queue(queue, item) {\n const new_pair = pair(item, null);\n if (is_empty_queue(queue)) {\n set_front_ptr(queue, new_pair);\n set_rear_ptr(queue, new_pair);\n } else {\n set_tail(rear_ptr(queue), new_pair);\n set_rear_ptr(queue, new_pair);\n }\n return queue;\n}\n```\n\nTo delete the item at the front of the queue, we merely modify the front pointer so that it now points at the second item\n\nin the queue, which can be found by following the tail pointer of the first item (see figure ):\n\n```javascript\nResult of using delete_queue(q)\n\t on the queue of figure.\n```\n\n```javascript\ndelete_queue\n is_empty_queue\n modify_pointers\n print_queue_example\n [ null, [ 'b', null ] ]\n\nfunction delete_queue(queue) {\n if (is_empty_queue(queue)) {\n error(queue, \"delete_queue called with an empty queue\");\n } else {\n set_front_ptr(queue, tail(front_ptr(queue)));\n return queue;\n }\n}\n```", + "token_count": 155, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Representing Queues", + "chunk_index": 4, + "chunk_id": "Modularity_Objects_and_State_Representing_Queues_4" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_13", - "chunk_index": 13, - "content": "pat) (extend-if-consistent pat dat frame)) ((and (pair? pat) (pair? dat)) (pattern-match (cdr pat) (cdr dat) (pattern-match (car pat) (car dat) frame))) (else 'failed))) function pattern_match(pattern, data, frame) { return frame === \"failed\" ? \"failed\" : equal(pattern, data) ? frame : is_variable(pattern) ? extend_if_consistent(pattern, data, frame) : is_pair(pattern) && is_pair(data) ? pattern_match(tail(pattern), tail(data), pattern_match(head(pattern), head(data), frame)) : \"failed\"; }\nHere is the procedure function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame: extend_if_consistent make_binding (define (extend-if-consistent var dat frame) (let ((binding (binding-in-frame var frame))) (if binding (pattern-match (binding-value binding) dat frame) (extend var dat frame)))) function extend_if_consistent(variable, data, frame) { const binding = binding_in_frame(variable, frame); return is_undefined(binding) ? extend(variable, data, frame) : pattern_match(binding_value(binding), data, frame); } If there is no binding for the variable in the frame, we simply add the binding of the variable to the data. Otherwise we match, in the frame, the data against the value of the variable in the frame.", - "token_count": 299, - "source_files": [ - "subsection4.xml" - ] + "content": "When we studied various ways of representing sets in chapter , we\nmentioned in section the task of\nmaintaining a table of records\n, we made extensive use of\ntwo-dimensional tables, in which information is stored and retrieved\nusing two keys.\n\nHere we see how to build tables as mutable list\nstructures.\n\nWe first consider a\nheads\npoint to successive records.\n\nThese gluing pairs are called the\nbackbone of the table.\n\nIn order to have a place that we can\nchange when we add a new record to the table, we build the table as a\nheaded list.\n\nA headed list has a special backbone pair at the\nbeginning, which holds a dummy record in this case\nthe arbitrarily chosen\nstring \"*table*\".\n\nFigure\nshows the box-and-pointer diagram for the table\n\n```javascript\na: 1\nb: 2\nc: 3\n```\n\nA table represented as a headed list.\n\nTo extract information from a table we use the\nfunction,\nwhich takes a key as argument and returns the associated value (or\nundefined\nif\nthere is no value stored under that key).\n\nThe function lookup\nis defined in terms of the\nThe function assoc\nreturns the record that has the given key as its\nhead.\n\nThe function lookup\nthen checks to see that the resulting record returned by\nundefined,\nand returns the value (the\ntail)\nof the record.\n\n```javascript\nlookup1_example\n make_table1\n insert_into_table1\n\nconst t = make_table();\ninsert(\"a\", 10, t);\nlookup(\"a\", t);\n```\n\n```javascript\nlookup1\n lookup1_example\n 10\n\nfunction lookup(key, table) {\n const record = assoc(key, tail(table));\n return is_undefined(record)\n ? undefined\n : tail(record);\n}\nfunction assoc(key, records) {\n return is_null(records)\n ? undefined\n : equal(key, head(head(records)))\n ? head(records)\n : assoc(key, tail(records));\n}\n```", + "token_count": 275, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Representing Tables", + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_Representing_Tables_1" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_14", - "chunk_index": 14, - "content": "If the stored value contains only constants, as it must if it was stored during pattern matching by extend-if-consistent , extend_if_consistent , then the match simply tests whether the stored and new values are the same. If so, it returns the unmodified frame; if not, it returns a failure indication. The stored value may, however, contain pattern variables if it was stored during unification (see section ). The recursive match of the stored pattern against the new data will add or check bindings for the variables in this pattern. For example, suppose we have a frame in which ?x $x is bound to (f ?y) list(\"f\", $y) and ?y $y is unbound, and we wish to augment this frame by a binding of ?x $x to (f b) . list(\"f\", \"b\") . We look up ?x $x and find that it is bound to (f ?y) . list(\"f\", $y) . This leads us to match (f ?y) list(\"f\", $y) against the proposed new value (f b) list(\"f\", \"b\") in the same frame. Eventually this match extends the frame by adding a binding of ?y $y to b . \"b\" . ?X The variable $x remains bound to (f ?y) . list(\"f\", $y) .", - "token_count": 285, - "source_files": [ - "subsection4.xml" - ] + "content": "The function lookup\nthen checks to see that the resulting record returned by\nundefined,\nand returns the value (the\ntail)\nof the record.\n\nTo insert a value in a table under a specified key, we first use\npairing\nthe key with the value, and insert this at the head of the table s\nlist of records, after the dummy record.\n\nIf there already is a record with\nthis key, we set the\ntail\nof this record to the designated new value.\n\nThe header of the table\nprovides us with a fixed location to modify in order to insert the new\nrecord.\n\n```javascript\nlookup1\n insert_into_table1\n\nfunction insert(key, value, table) {\n const record = assoc(key, tail(table));\n if (is_undefined(record)) {\n set_tail(table,\n pair(pair(key, value), tail(table)));\n } else {\n set_tail(record, value);\n }\n return \"ok\";\n}\n```\n\nTo construct a new table, we simply create a list containing just the string\n\n```javascript\nmake_table1\n\nfunction make_table() {\n return list(\"*table*\");\n}\n```\n\nIn a two-dimensional table, each value is indexed by two keys.\n\nWe can\nconstruct such a table as a one-dimensional table in which each key\nidentifies a subtable.\n\nFigure\nshows the box-and-pointer diagram for the table\n\n```javascript\n\"math\":\n \"+\": 43\n \"-\": 45\n \"*\": 42\n\"letters\":\n \"a\": 97\n \"b\": 98\n```\n\nwhich has two subtables.\n\n(The subtables don t need a special header\nstring,\nsince the key that identifies the subtable serves this purpose.)\nA two-dimensional table.\n\nWhen we look up an item, we use the first key to identify the correct\nsubtable.\n\nThen we use the second key to identify the record within the\nsubtable.\n\n```javascript\nlookup2_example\n make_table2\n insert_into_table2\n\nconst t = list(\"*table*\");\ninsert(\"a\", \"b\", 10, t);\nlookup(\"a\", \"b\", t);\n```\n\n```javascript\njust_assoc\n\nfunction assoc(key, records) {\n return is_null(records)\n ? undefined\n : equal(key, head(head(records)))\n ? head(records)\n : assoc(key, tail(records));\n}\n```", + "token_count": 295, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Representing Tables", + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_Representing_Tables_2" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_15", - "chunk_index": 15, - "content": "We never modify a stored binding and we never store more than one binding for a given variable. The procedures functions used by extend-if-consistent extend_if_consistent to manipulate bindings are defined in section . If a pattern contains a dot followed by a pattern variable, the pattern variable matches the rest of the data list (rather than the next element of the data list), just as one would expect with the . Although the pattern matcher we have just implemented doesn t look for dots, it does behave as we want. This is because the Lisp read primitive, which is used by query-driver-loop to read the query and represent it as a list structure, treats dots in a special way. When read sees a car of a cons whose cdr will be the rest of the list) it makes the next item be the cdr of the list structure. For example, the list structure produced by read for the pattern (computer ?type) could be constructed by evaluating the expression (cons 'computer (cons '?type '())) , and that for (computer ?type) could be constructed by evaluating the expression (cons 'computer '?type) . Thus, as pattern-match recursively compares car s and cdr s of a data list and a pattern that had a dot, it eventually matches the variable after the dot (which is a cdr of the pattern) against a sublist of the data list, binding the variable to that list.", - "token_count": 281, - "source_files": [ - "subsection4.xml" - ] + "content": "Then we use the second key to identify the record within the\nsubtable.\n\n```javascript\nlookup2\n just_assoc\n lookup2_example\n 10\n\nfunction lookup(key_1, key_2, table) {\n const subtable = assoc(key_1, tail(table));\n if (is_undefined(subtable)) {\n return undefined;\n } else {\n const record = assoc(key_2, tail(subtable));\n return is_undefined(record)\n ? undefined\n : tail(record);\n }\n}\n```\n\nTo insert a new item under a pair of keys, we use (key_2,\n\n```javascript\njust_assoc\n insert_into_table2\n\nfunction insert(key_1, key_2, value, table) {\n const subtable = assoc(key_1, tail(table));\n if (is_undefined(subtable)) {\n set_tail(table,\n pair(list(key_1, pair(key_2, value)), tail(table)));\n } else {\n const record = assoc(key_2, tail(subtable));\n if (is_undefined(record)) {\n set_tail(subtable,\n pair(pair(key_2, value), tail(subtable)));\n } else {\n set_tail(record, value);\n }\n }\n return \"ok\";\n}\n```\n\nThe\ninsert\noperations defined above take the table as an argument.\n\nThis enables us to\nuse programs that access more than one table.\n\nAnother way to deal with\nmultiple tables is to have separate\ninsert\nfunctions\nfor each table.\n\nWe can do this by representing a table procedurally, as an\nobject that maintains an internal table as part of its local state.\n\nWhen\nsent an appropriate message, this table object supplies the\nfunction\nwith which to operate on the internal table.\n\nHere is a generator for\ntwo-dimensional tables represented in this fashion:", + "token_count": 203, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Representing Tables", + "chunk_index": 3, + "chunk_id": "Modularity_Objects_and_State_Representing_Tables_3" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_16", - "chunk_index": 16, - "content": "For example, matching the pattern (computer ?type) against (computer programmer trainee) will match ?type against the list (programmer trainee) . Apply-rules The function apply_rules is the rule analog of find-assertions find_assertions (section ). It takes as input a pattern and a frame, and it forms a stream of extension frames by applying rules from the data base. Stream-flatmap The function stream_flatmap maps apply-a-rule apply_a_@rule down the stream of possibly applicable rules (selected by fetch-rules , fetch_rules , section ) and combines the resulting streams of frames. apply_rules stream_flatmap apply_a_rule fetch_rules (define (apply-rules pattern frame) (stream-flatmap (lambda (rule) (apply-a-rule rule pattern frame)) (fetch-rules pattern frame))) function apply_rules(pattern, frame) { return stream_flatmap(rule => apply_a_rule(rule, pattern, frame), fetch_rules(pattern, frame)); }\nApply-a-rule applies rules The function apply_a_rule applies a rule using the method outlined in section . It first augments its argument frame by unifying the rule conclusion with the pattern in the given frame. If this succeeds, it evaluates the rule body in this new frame. Before any of this happens, however, the program renames all the variables in the rule with unique new names. The reason for this is to prevent the variables for different rule applications from becoming confused with each other.", - "token_count": 296, - "source_files": [ - "subsection4.xml" - ] + "content": "Here is a generator for\ntwo-dimensional tables represented in this fashion:\n\n```javascript\nmake_table2\n just_assoc\n\nfunction make_table() {\n const local_table = list(\"*table*\");\n function lookup(key_1, key_2) {\n const subtable = assoc(key_1, tail(local_table));\n if (is_undefined(subtable)) {\n return undefined;\n } else {\n const record = assoc(key_2, tail(subtable));\n return is_undefined(record)\n ? undefined\n : tail(record);\n }\n }\n function insert(key_1, key_2, value) {\n const subtable = assoc(key_1, tail(local_table));\n if (is_undefined(subtable)) {\n set_tail(local_table,\n pair(list(key_1, pair(key_2, value)),\n tail(local_table)));\n } else {\n const record = assoc(key_2, tail(subtable));\n if (is_undefined(record)) {\n set_tail(subtable,\n pair(pair(key_2, value), tail(subtable)));\n } else {\n set_tail(record, value);\n }\n }\n }\n function dispatch(m) {\n return m === \"lookup\"\n ? lookup\n : m === \"insert\"\n ? insert\n : error(m, \"unknown operation -- table\");\n }\n return dispatch;\n}\n```\n\nUsing make_table, we could for data-directed programming, as follows:\n\n```javascript\noperation_table_example\n\nput(\"a\", \"b\", 10);\nget(\"a\", \"b\");\n```\n\n```javascript\noperation_table\n make_table2\n operation_table_example\n 10\n\nconst operation_table = make_table();\nconst get = operation_table(\"lookup\");\nconst put = operation_table(\"insert\");\n```\n\nThe function get takes as arguments two keys, and make_table.", + "token_count": 163, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Modeling with Mutable Data", + "subsection": "Representing Tables", + "chunk_index": 4, + "chunk_id": "Modularity_Objects_and_State_Representing_Tables_4" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_17", - "chunk_index": 17, - "content": "For instance, if two rules both use a variable named ?x , named $x , then each one may add a binding for ?x $x to the frame when it is applied. These two ?x s $x s have nothing to do with each other, and we should not be fooled into thinking that the two bindings must be consistent. Rather than rename variables, we could devise a more clever environment structure; however, the renaming approach we have chosen here is the most straightforward, even if not the most efficient. (See exercise .) Here is the apply-a-rule apply_a_rule procedure: function: apply_a_rule rename_variables_in unify_match singleton_stream is_rule (define (apply-a-rule rule query-pattern query-frame) (let ((clean-rule (rename-variables-in rule))) (let ((unify-result (unify-match query-pattern (conclusion clean-rule) query-frame))) (if (eq? unify-result 'failed) the-empty-stream (qeval (rule-body clean-rule) (singleton-stream unify-result)))))) function apply_a_rule(rule, query_pattern, query_frame) { const clean_rule = rename_variables_in(rule); const unify_result = unify_match(query_pattern, conclusion(clean_rule), query_frame); return unify_result === \"failed\" ? null : evaluate_query(rule_body(clean_rule), singleton_stream(unify_result)); } The selectors rule-body rule_body and conclusion that extract parts of a rule are defined in section .", - "token_count": 291, - "source_files": [ - "subsection4.xml" - ] + "content": "We ve gained a good understanding of assignment as a tool in modeling,\nas well as an appreciation of the complex problems that assignment\nraises.\n\nIt is time to ask whether we could have gone about things in a\ndifferent way, so as to avoid some of these problems.\n\nIn this\nsection, we explore an alternative approach to modeling state, based\non data structures called streams.\n\nAs we shall see, streams can\nmitigate some of the complexity of modeling state.\n\nLet s step back and review where this complexity comes from.\n\nIn an\nattempt to model real-world phenomena, we made some apparently\nreasonable decisions: We modeled real-world objects with local state\nby computational objects with local variables.\n\nWe identified time\nvariation in the real world with time variation in the computer.\n\nWe\nimplemented the time variation of the states of the model objects in\nthe computer with assignments to the local variables of the model\nobjects.\n\nIs there another approach?\n\nCan we avoid identifying time in the\ncomputer with time in the modeled world?\n\nMust we make the model\nchange with time in order to model phenomena in a changing world?\n\nThink about the issue in terms of mathematical functions.\n\nWe can\ndescribe the time-varying behavior of a quantity\n$x$ as a function of time\n$x(t)$.\n\nIf we concentrate on $x$ instant by instant,\nwe think of it as a changing quantity.\n\nYet if we concentrate on the entire\ntime history of values, we do not emphasize change the function\nitself does not change.\n\nIf time is measured in discrete steps, then we can model a time function as\na (possibly infinite) sequence.\n\nIn this section, we will see how to\nmodel change in terms of sequences that represent the time histories\nof the systems being modeled.", + "token_count": 297, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_Streams_1" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_18", - "chunk_index": 18, - "content": "We generate unique variable names by associating a unique identifier (such as a number) with each rule application and combining this identifier with the original variable names. For example, if the rule-application identifier is 7, we might change each ?x $x in the rule to ?x-7 $x_7 and each ?y $y in the rule to ?y-7 . $y_7 . ( Make-new-variable (The functions make_new_variable and new-rule-application-id new_rule_application_id are included with the syntax procedures functions in section .) rename_variables_in is_variable_4 (define (rename-variables-in rule) (let ((rule-application-id (new-rule-application-id))) (define (tree-walk exp) (cond ((var? exp) (make-new-variable exp rule-application-id)) ((pair? exp) (cons (tree-walk (car exp)) (tree-walk (cdr exp)))) (else exp))) (tree-walk rule))) function rename_variables_in(rule) { const rule_application_id = new_rule_application_id(); function tree_walk(exp) { return is_variable(exp) ? make_new_variable(exp, rule_application_id) : is_pair(exp) ? pair(tree_walk(head(exp)), tree_walk(tail(exp))) : exp; } return tree_walk(rule); }\nThe procedure function that takes as inputs two patterns and a frame and returns either the extended frame or the symbol failed . string \"failed\" . The unifier is like the pattern matcher except that it is symmetrical variables are allowed on both sides of the match. Unify-match The function unify_match is basically the same as pattern-match , pattern_match , except that there is extra code an extra clause (marked *** below) to handle the case where the object on the right side of the match is a variable. unify_match extend_if_possible variable (define (unify-match p1 p2 frame) (cond ((eq? frame 'failed) 'failed) ((equal? p1 p2) frame) ((var? p1) (extend-if-possible p1 p2 frame)) ((var? p2) (extend-if-possible p2 p1 frame)) ; *** ((and (pair? p1) (pair? p2)) (unify-match (cdr p1) (cdr p2) (unify-match (car p1) (car p2) frame))) (else 'failed))) function unify_match(p1, p2, frame) { return frame === \"failed\" ? \"failed\" : equal(p1, p2) ? frame : is_variable(p1) ? extend_if_possible(p1, p2, frame) : is_variable(p2) // *** ? extend_if_possible(p2, p1, frame) // *** : is_pair(p1) && is_pair(p2) ? unify_match(tail(p1), tail(p2), unify_match(head(p1), head(p2), frame)) : \"failed\"; }", - "token_count": 606, - "source_files": [ - "subsection4.xml" - ] + "content": "In this section, we will see how to\nmodel change in terms of sequences that represent the time histories\nof the systems being modeled.\n\nTo accomplish this, we introduce new\ndata structures called streams.\n\nFrom an abstract point of view,\na stream is simply a sequence.\n\nHowever, we will find that the\nstraightforward implementation of streams as lists (as in\nsection ) doesn t fully reveal\nthe power of stream processing.\n\nAs an alternative, we introduce the\ntechnique of\ndelayed evaluation , which enables us to represent\nvery large (even infinite) sequences as streams.\n\nStream processing lets us model systems that have state without ever\nusing assignment or mutable data.\n\nThis has important implications,\nboth theoretical and practical, because we can build models that avoid\nthe drawbacks inherent in introducing assignment.\n\nOn the other hand,\nthe stream framework raises difficulties of its own, and the question\nof which modeling technique leads to more modular and more easily\nmaintained systems remains open.", + "token_count": 161, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_Streams_2" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_19", - "chunk_index": 19, - "content": "In unification, as in one-sided pattern matching, we want to accept a proposed extension of the frame only if it is consistent with existing bindings. The procedure function extend-if-possible extend_if_possible used in unification is the same as the extend-if-consistent function extend_if_consistent used in pattern matching except for two special checks, marked *** in the program below. In the first case, if the variable we are trying to match is not bound, but the value we are trying to match it with is itself a (different) variable, it is necessary to check to see if the value is bound, and if so, to match its value. If both parties to the match are unbound, we may bind either to the other.", - "token_count": 150, - "source_files": [ - "subsection4.xml" - ] + "content": "Streams with delayed evaluation can be a powerful modeling tool,\nproviding many of the benefits of local state and assignment.\n\nMoreover, they avoid some of the theoretical tangles that accompany\nthe introduction of assignment into a programming language.\n\nThe stream approach can be illuminating because it allows us to build systems with different\n\nIn section , we introduced\niterative processes, which proceed by updating state variables.\n\nWe know now\nthat we can represent state as a timeless stream of values\nrather than as a set of variables to be updated.\n\nLet s adopt this\nperspective in revisiting the square-root\nfunction\nfrom section.\n\nRecall that the idea is to\ngenerate a sequence of better and better guesses for the square root of\n$x$ by applying over and over again the\nfunction\nthat improves guesses:\n\n```javascript\nsqrt_improve_example\n\nsqrt_improve(1.2, 2);\n```\n\n```javascript\nsqrt_improve\n average_definition\n sqrt_improve_example\n 1.4333333333333333\n\nfunction sqrt_improve(guess, x) {\n return average(guess, x / guess);\n}\n```\n\nIn our original\nfunction,\nwe made these guesses be the successive values of a state variable.\n\nInstead\nwe can generate the infinite stream of guesses, starting with an initial\nguess of 1:\n\n```javascript\nsqrt_stream\n sqrt_improve\n sqrt_stream_example\n\nfunction sqrt_stream(x) {\n return pair(1, () => stream_map(guess => sqrt_improve(guess, x),\n sqrt_stream(x)));\n}\n```\n\n```javascript\nsqrt_stream_example\n display_stream\n sqrt_stream\n 1.414213562373095\n\ndisplay_stream(sqrt_stream(2));\n\nstream_ref(sqrt_stream(2), 5);\n```\n\nWe can generate more and more terms of the stream to get better and\nbetter guesses.\n\nIf we like, we can write a\nfunction\nthat keeps generating terms until the answer is good enough.\n\n(See exercise.)\n\nAnother iteration that we can treat in the same way is to generate an\napproximation to\n$\\pi$ , based upon the\nalternating series that we saw in\nsection :\n\\[\n\\begin{array}{lll}\n\\dfrac {\\pi}{4} &=& 1-\\dfrac{1}{3}+\\dfrac{1}{5}-\\dfrac{1}{7}+\\cdots\n\\end{array}\n\\]\nWe first generate the stream of summands of the series (the reciprocals\nof the odd integers, with alternating signs).", + "token_count": 305, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Exploiting the Stream Paradigm", + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_Exploiting_the_Stream_Paradigm_1" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_20", - "chunk_index": 20, - "content": "The second check deals with attempts to bind a variable to a pattern that includes that variable. Such a situation can occur whenever a variable is repeated in both patterns. Consider, for example, unifying the two patterns (?x ?x) list($x, $x) and (?y $\\langle expression$ $involving$ ?y $\\rangle$ ) list($y, $\\langle$ expression involving $y $\\rangle$ ) in a frame where both ?x $x and ?y $y are unbound. First ?x $x is matched against ?y , $y , making a binding of ?x $x to ?y . $y . Next, the same ?x $x is matched against the given expression involving ?y . $y . Since ?x $x is already bound to ?y , $y , this results in matching ?y $y against the expression. expression. If we think of the unifier as finding a set of values for the pattern variables that make the patterns the same, then these patterns imply instructions to find a ?y $y such that ?y $y is equal to the expression involving ?y . $y . There is no general method for solving such equations, so we We reject such bindings; these cases are recognized by the predicate depends-on? depends_on . (?x ?x) list($x, $x) and (?y ?y) . list($y, $y) . The second attempt to bind ?x $x to ?y $y matches ?y $y (the stored value of ?x (the stored value of $x ) against ?y $y (the new value of ?x ). (the new value of $x ). This is taken care of by the equal? equal clause of unify-match . unify_match . extend_if_possible make_binding depends_on variable (define (extend-if-possible var val frame) (let ((binding (binding-in-frame var frame))) (cond (binding (unify-match (binding-value binding) val frame)) ((var? val) ; *** (let ((binding (binding-in-frame val frame))) (if binding (unify-match var (binding-value binding) frame) (extend var val frame)))) ((depends-on? val var frame) ; *** 'failed) (else (extend var val frame))))) function extend_if_possible(variable, value, frame) { const binding = binding_in_frame(variable, frame); if (! is_undefined(binding)) { return unify_match(binding_value(binding), value, frame); } else if (is_variable(value)) { // *** const binding = binding_in_frame(value, frame); return ! is_undefined(binding) ? unify_match(variable, binding_value(binding), frame) : extend(variable, value, frame); } else if (depends_on(value, variable, frame)) { // *** return \"failed\"; } else { return extend(variable, value, frame); } }", - "token_count": 595, - "source_files": [ - "subsection4.xml" - ] + "content": "Another iteration that we can treat in the same way is to generate an\napproximation to\n$\\pi$ , based upon the\nalternating series that we saw in\nsection :\n\\[\n\\begin{array}{lll}\n\\dfrac {\\pi}{4} &=& 1-\\dfrac{1}{3}+\\dfrac{1}{5}-\\dfrac{1}{7}+\\cdots\n\\end{array}\n\\]\nWe first generate the stream of summands of the series (the reciprocals\nof the odd integers, with alternating signs).\n\nThen we take the stream\nof sums of more and more terms (using the\npartial_sums function\nof exercise ) and scale the result by 4:\n\n```javascript\npi_stream\n display_stream\n scale_stream\n partial_sums\n pi_stream_example\n\nfunction pi_summands(n) {\n return pair(1 / n, () => stream_map(x => - x, pi_summands(n + 2)));\n}\nconst pi_stream = scale_stream(partial_sums(pi_summands(1)), 4);\n```\n\n```javascript\npi_stream_example\n pi_stream\n 3.017071817071818\n\ndisplay_stream(pi_stream);\n\nstream_ref(pi_stream, 7);\n```\n\nThis gives us a stream of better and better approximations to\n$\\pi$ , although the approximations converge\nrather slowly.\n\nEight terms of the sequence bound the value of\n$\\pi$ between 3.284 and 3.017.\n\nSo far, our use of the stream of states approach is not much different\nfrom updating state variables.\n\nBut streams give us an opportunity to do\nsome interesting tricks.\n\nFor example, we can transform a stream with a\nsequence accelerator that converts a sequence of approximations to a\nnew sequence that converges to the same value as the original, only faster.\n\nOne such accelerator, due to the eighteenth-century Swiss mathematician s technique, if $S_n$ is the $n$ th term of the original sum sequence, then the\n\naccelerated sequence has terms \\[ \\begin{array}{l} S_{n+1} - \\dfrac{(S_{n+1}-S_n)^2}{S_{n-1}-2S_n+S_{n+1}} \\end{array} \\] Thus, if the original sequence is represented as a stream of values, the transformed\n\nsequence is given by", + "token_count": 266, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Exploiting the Stream Paradigm", + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_Exploiting_the_Stream_Paradigm_2" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_21", - "chunk_index": 21, - "content": "Nevertheless, most logic programming systems today allow cyclic references, by accepting the cyclic data structure as the result of the match. This is justified theoretically using rational trees", - "token_count": 31, - "source_files": [ - "subsection4.xml" - ] + "content": "sequence is given by\n\n```javascript\neuler_transform\n square_definition\n memo\n euler_transform_example\n 3.1418396189294033\n\nfunction euler_transform(s) {\n const s0 = stream_ref(s, 0); // $S_{n-1}$\n const s1 = stream_ref(s, 1); // $S_{n}$\n const s2 = stream_ref(s, 2); // $S_{n+1}$\n return pair(s2 - square(s2 - s1) / (s0 + (-2) * s1 + s2),\n memo(() => euler_transform(stream_tail(s))));\n}\n```\n\n```javascript\nNote that we make use of the memoization optimization of\n\t section, because in the\n\t following we will rely on repeated evaluation of the resulting stream.\n```\n\nWe can demonstrate Euler acceleration with our sequence of approximations to $\\pi$ :\n\n```javascript\neuler_transform_example\n euler_transform\n display_stream\n pi_stream\n 3.1418396189294033\n\ndisplay_stream(euler_transform(pi_stream));\n\nstream_ref(euler_transform(pi_stream), 8);\n```\n\nEven better, we can accelerate the accelerated sequence, and recursively\naccelerate that, and so on.\n\nNamely, we create a stream of streams (a\nstructure we ll call a\ntableau ) in which each stream is the transform of the preceding one:\n\n```javascript\nmake_tableau\n\nfunction make_tableau(transform, s) {\n return pair(s, () => make_tableau(transform, transform(s)));\n}\n```\n\nThe tableau has the form \\[ \\begin{array}{llllll} s_{00} & s_{01} & s_{02} & s_{03} & s_{04} & \\ldots\\\\ & s_{10} & s_{11} & s_{12} &\n\ns_{13} & \\ldots\\\\ & & s_{20} & s_{21} & s_{22} & \\ldots\\\\ & & & & \\ldots & \\end{array} \\] Finally, we form a sequence\n\nby taking the first term in each row of the tableau:\n\n```javascript\naccelerated_sequence\n make_tableau\n accelerated_sequence_example\n\nfunction accelerated_sequence(transform, s) {\n return stream_map(head, make_tableau(transform, s));\n}\n```\n\nWe can demonstrate this kind of super-acceleration of the $\\pi$ sequence:\n\n```javascript\naccelerated_sequence_example\n accelerated_sequence\n euler_transform\n pi_stream\n display_stream\n 3.1415926535911765\n\ndisplay_stream(accelerated_sequence(euler_transform, pi_stream));\n\nstream_ref(accelerated_sequence(euler_transform, pi_stream),\n 6);\n```\n\nThe result is impressive.\n\nTaking eight terms of the sequence yields the\ncorrect value of $\\pi$ to 14 decimal places.", + "token_count": 277, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Exploiting the Stream Paradigm", + "chunk_index": 3, + "chunk_id": "Modularity_Objects_and_State_Exploiting_the_Stream_Paradigm_3" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_22", - "chunk_index": 22, - "content": "Depends-on? The function depends_on is a predicate that tests whether an expression proposed to be the value of a pattern variable depends on the variable. This must be done relative to the current frame because the expression may contain occurrences of a variable that already has a value that depends on our test variable. The structure of depends-on? depends_on is a simple recursive tree walk in which we substitute for the values of variables whenever necessary. depends_on variable make_binding (define (depends-on? exp var frame) (define (tree-walk e) (cond ((var? e) (if (equal? var e) true (let ((b (binding-in-frame e frame))) (if b (tree-walk (binding-value b)) false)))) ((pair? e) (or (tree-walk (car e)) (tree-walk (cdr e)))) (else false))) (tree-walk exp)) function depends_on(expression, variable, frame) { function tree_walk(e) { if (is_variable(e)) { if (equal(variable, e)) { return true; } else { const b = binding_in_frame(e, frame); return is_undefined(b) ? false : tree_walk(binding_value(b)); } } else { return is_pair(e) ? tree_walk(head(e)) || tree_walk(tail(e)) : false; } } return tree_walk(expression); }\nOne important problem in designing logic programming languages is that of arranging things so that as few irrelevant Then, in addition to storing all assertions in one big stream, we store all assertions whose car s are constant symbols in separate streams, in a table indexed by the symbol. To fetch an assertion that may match a pattern, we first check to see if the car of the pattern is a constant symbol. If so, we return (to be tested using the matcher) all the stored assertions that have the same car . If the pattern s car is not a constant symbol, we return all the stored assertions. Cleverer methods could also take advantage of information in the frame, or try also to optimize the case where the car of the pattern is not a constant symbol. We avoid building our criteria for indexing (using the car , handling only the case of constant symbols) into the program; instead we call on predicates and selectors that embody our criteria. We store the assertions in separate streams, one for each kind of information, in a table indexed by the kind. To fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information). Cleverer methods could also take advantage of information in the frame. We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria. fetch_assertions get_stream index_key_of (define THE-ASSERTIONS the-empty-stream) (define (fetch-assertions pattern frame) (if (use-index? pattern) (get-indexed-assertions pattern) (get-all-assertions))) (define (get-all-assertions) THE-ASSERTIONS) (define (get-indexed-assertions pattern) (get-stream (index-key-of pattern) 'assertion-stream)) function fetch_assertions(pattern, frame) { return get_indexed_assertions(pattern); } function get_indexed_assertions(pattern) { return get_stream(index_key_of(pattern), \"assertion-stream\"); } Get-stream The function get_stream looks up a stream in the table and returns an empty stream if nothing is stored there. get_stream operation_table_from_chapter_3 operation_table (define (get-stream key1 key2) (let ((s (get key1 key2))) (if s s the-empty-stream))) function get_stream(key1, key2) { const s = get(key1, key2); return is_undefined(s) ? null : s; }", - "token_count": 794, - "source_files": [ - "subsection4.xml" - ] + "content": "Taking eight terms of the sequence yields the\ncorrect value of $\\pi$ to 14 decimal places.\n\nIf we had used only the original $\\pi$ sequence,\nwe would need to compute on the order of $10^{13}$\nterms (i.e., expanding the series far enough so that the individual terms\nare less then $10^{-13}$ ) to get that much\naccuracy!\n\nWe could have implemented these acceleration techniques without using\nstreams.\n\nBut the stream formulation is particularly elegant and convenient\nbecause the entire sequence of states is available to us as a data structure\nthat can be manipulated with a uniform set of operations.\n\nIn section , we saw how the\nsequence paradigm handles traditional nested loops as processes defined\non sequences of pairs.\n\nIf we generalize this technique to infinite streams,\nthen we can write programs that are not easily represented as loops, because\nthe looping must range over an infinite set.\n\nFor example, suppose we want to generalize the\nprime_sum_pairs function\nof section to produce the stream\nof pairs of all integers $(i,j)$ with\n$i \\leq j$ such that\n$i+j$\nis prime.\n\nIf\nint_pairs\nis the sequence of all pairs of integers $(i,j)$\nwith $i \\leq j$ , then our required stream is\nsimply\n\n```javascript\nint_pairs\n pairs_second_attempt\n integers_definition\n display_stream\n\nconst int_pairs = pairs(integers, integers);\n```\n\n```javascript\nprime_sum_pairs_stream\n is_prime2\n int_pairs\n [ 1, [ 12, null ] ]\n\nstream_filter(pair => is_prime(head(pair) + head(tail(pair))),\n int_pairs);\n\nstream_ref(stream_filter(pair => is_prime(head(pair) + head(tail(pair))),\n int_pairs), 8);\n```\n\nOur problem, then, is to produce the stream\nint_pairs.", + "token_count": 247, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Exploiting the Stream Paradigm", + "chunk_index": 4, + "chunk_id": "Modularity_Objects_and_State_Exploiting_the_Stream_Paradigm_4" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_23", - "chunk_index": 23, - "content": "Rules are stored similarly, using the car of the rule conclusion. Rule conclusions are arbitrary patterns, however, so they differ from assertions in that they can contain variables. A pattern whose car is a constant symbol can match rules whose conclusions start with a variable as well as rules whose conclusions have the same car . Thus, when fetching rules that might match a pattern whose car is a constant symbol we fetch all rules whose conclusions start with a variable as well as those whose conclusions have the same car as the pattern. For this purpose we store all rules whose conclusions start with a variable in a separate stream in our table, indexed by the symbol ? . Rules are stored similarly, using the head of the rule conclusion. A pattern can match rules whose conclusions have the same head. Thus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern. fetch_rules get_stream index_key_of (define THE-RULES the-empty-stream) (define (fetch-rules pattern frame) (if (use-index? pattern) (get-indexed-rules pattern) (get-all-rules))) (define (get-all-rules) THE-RULES) (define (get-indexed-rules pattern) (stream-append (get-stream (index-key-of pattern) 'rule-stream) (get-stream '?", - "token_count": 269, - "source_files": [ - "subsection4.xml" - ] + "content": "Our problem, then, is to produce the stream\nint_pairs.\n\nMore generally, suppose we have two streams\n$S = (S_i)$ and\n$T = (T_j)$ ,\nand imagine the infinite rectangular array\n\\[\n\\begin{array}{cccc}\n(S_0,T_0) & (S_0,T_1) & (S_0, T_2) & \\ldots\\\\\n(S_1,T_0) & (S_1,T_1) & (S_1, T_2) & \\ldots\\\\\n(S_2,T_0) & (S_2,T_1) & (S_2, T_2) & \\ldots\\\\\n\\ldots\n\\end{array}\n\\]\nWe wish to generate a stream that contains all the pairs in the array\nthat lie on or above the diagonal, i.e., the pairs\n\\[\n\\begin{array}{cccc}\n(S_0,T_0) & (S_0,T_1) & (S_0, T_2) & \\ldots\\\\\n& (S_1,T_1) & (S_1, T_2) & \\ldots\\\\\n& & (S_2, T_2) & \\ldots\\\\\n& & & \\ldots\n\\end{array}\n\\]\n(If we take both $S$ and\n$T$ to be the stream of integers, then this\nwill be our desired stream\nint_pairs.)\n\nCall the general stream of pairs\npairs(S, T),\nand consider it to be composed of three parts: the pair\n$(S_0,T_0)$ , the rest of the pairs in the first\nrow, and the remaining pairs:\n\\[\n\\begin{array}{c|ccc}\n(S_0,T_0) & (S_0,T_1) & (S_0, T_2) & \\ldots\\\\\n\\hline{} %--------------------------------------------------- \\\\\n& (S_1,T_1) & (S_1, T_2) & \\ldots\\\\\n& & (S_2, T_2) & \\ldots\\\\\n& & & \\ldots\n\\end{array}\n\\]\nObserve that the third piece in this decomposition (pairs that are not in\nthe first row) is (recursively) the pairs formed from\nstream_tail(S)\nand\nstream_tail(T).\n\nAlso note that the second piece (the rest of the first row) is\n\n```javascript\nstream_map(x => list(head(s), x),\n stream_tail(t));\n```\n\nThus we can form our stream of pairs as follows:\n\n```javascript\nfunction pairs(s, t) {\n return pair(list(head(s), head(t)),\n () => combine-in-some-way(\n stream_map(x => list(head(s), x),\n stream_tail(t)),\n pairs(stream_tail(s), stream_tail(t))));\n}\n```\n\nIn order to complete the function, we must choose some way to function from section :", + "token_count": 290, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Exploiting the Stream Paradigm", + "chunk_index": 5, + "chunk_id": "Modularity_Objects_and_State_Exploiting_the_Stream_Paradigm_5" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_24", - "chunk_index": 24, - "content": "'rule-stream))) function fetch_rules(pattern, frame) { return get_indexed_rules(pattern); } function get_indexed_rules(pattern) { return get_stream(index_key_of(pattern), \"rule-stream\"); }", - "token_count": 41, - "source_files": [ - "subsection4.xml" - ] + "content": "In order to complete the function, we must choose some way to function from section :\n\n```javascript\nstream_append_example\n\nconst ones = pair(1, () => ones);\nconst twos = pair(2, () => twos);\nconst appended = stream_append(ones, twos);\nstream_ref(appended, 100);\n```\n\n```javascript\nstream_append\n stream_append_example\n 1\n\nfunction stream_append(s1, s2) {\n return is_null(s1)\n ? s2\n : pair(head(s1),\n () => stream_append(stream_tail(s1), s2));\n}\n```\n\nThis is unsuitable for infinite streams, however, because it takes all the\nelements from the first stream before incorporating the second stream.\n\nIn\nparticular, if we try to generate all pairs of positive integers using\n\n```javascript\npairs_first_attempt\n stream_append\n\nfunction pairs(s, t) {\n return pair(list(head(s), head(t)),\n () => stream_append(\n stream_map(x => list(head(s), x),\n stream_tail(t)),\n pairs(stream_tail(s), stream_tail(t)))\n );\n}\n```\n\n```javascript\npairs_first_attempt_usage\n pairs_first_attempt\n pairs_first_attempt_example\n [ 1, [ 9, null ] ]\n\npairs(integers, integers);\n```\n\n```javascript\npairs_first_attempt_example\n display_stream\n integers_definition\n\ndisplay_stream(pairs(integers, integers));\n\nstream_ref(pairs(integers, integers), 8);\n```\n\nour stream of results will first try to run through all pairs with the first integer equal to 1, and hence will never produce pairs\n\nwith any other value of the first integer.\n\nTo handle infinite streams, we need to devise an order of combination\nthat ensures that every element will eventually be reached if we let\nour program run long enough.\n\nAn elegant way to accomplish this is\nwith the following function :\n\n```javascript\ninterleave_example\n display_stream\n\nconst ones = pair(1, () => ones);\nconst twos = pair(2, () => twos);\nconst interleaved = interleave(ones, twos);\ndisplay_stream(interleaved);\n\nconst ones = pair(1, () => ones);\nconst twos = pair(2, () => twos);\nconst interleaved = interleave(ones, twos);\nstream_ref(interleaved, 7);\n```\n\n```javascript\ninterleave\n interleave_example\n 2\n\nfunction interleave(s1, s2) {\n return is_null(s1)\n ? s2\n : pair(head(s1),\n () => interleave(s2, stream_tail(s1)));\n}\n```\n\nSince\n\nWe can thus generate the required stream of pairs as", + "token_count": 292, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Exploiting the Stream Paradigm", + "chunk_index": 6, + "chunk_id": "Modularity_Objects_and_State_Exploiting_the_Stream_Paradigm_6" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_25", - "chunk_index": 25, - "content": "Add-rule-or-assertion! The function add_rule_or_assertion is used by query-driver-loop query_driver_loop to add assertions and rules to the data base. Each item is stored in the index. add_rule_or_assertion is_rule store_assertion_in_index fetch_assertions fetch_rules (define (add-rule-or-assertion! assertion) (if (rule? assertion) (add-rule! assertion) (add-assertion! assertion))) (define (add-assertion! assertion) (store-assertion-in-index assertion) (let ((old-assertions THE-ASSERTIONS)) (set! THE-ASSERTIONS (cons-stream assertion old-assertions)) 'ok)) (define (add-rule! rule) (store-rule-in-index rule) (let ((old-rules THE-RULES)) (set! THE-RULES (cons-stream rule old-rules)) 'ok)) function add_rule_or_assertion(assertion) { return is_rule(assertion) ? add_rule(assertion) : add_assertion(assertion); } function add_assertion(assertion) { store_assertion_in_index(assertion); return \"ok\"; } function add_rule(rule) { store_rule_in_index(rule); return \"ok\"; }\nTo actually store an assertion or a rule, we store it in the appropriate stream. store_assertion_in_index operation_table_from_chapter_3 operation_table index_key_of get_stream is_rule (define (store-assertion-in-index assertion) (if (indexable? assertion) (let ((key (index-key-of assertion))) (let ((current-assertion-stream (get-stream key 'assertion-stream))) (put key 'assertion-stream (cons-stream assertion current-assertion-stream)))))) (define (store-rule-in-index rule) (let ((pattern (conclusion rule))) (if (indexable? pattern) (let ((key (index-key-of pattern))) (let ((current-rule-stream (get-stream key 'rule-stream))) (put key 'rule-stream (cons-stream rule current-rule-stream))))))) function store_assertion_in_index(assertion) { const key = index_key_of(assertion); const current_assertion_stream = get_stream(key, \"assertion-stream\"); put(key, \"assertion-stream\", pair(assertion, () => current_assertion_stream)); } function store_rule_in_index(rule) { const pattern = conclusion(rule); const key = index_key_of(pattern); const current_rule_stream = get_stream(key, \"rule-stream\"); put(key, \"rule-stream\", pair(rule, () => current_rule_stream)); }", - "token_count": 528, - "source_files": [ - "subsection4.xml" - ] + "content": "We can thus generate the required stream of pairs as\n\n```javascript\npairs_second_attempt\n interleave\n pairs_second_attempt_example\n [ 2, [ 4, null ] ]\n\nfunction pairs(s, t) {\n return pair(list(head(s), head(t)),\n () => interleave(stream_map(x => list(head(s), x),\n stream_tail(t)),\n pairs(stream_tail(s),\n stream_tail(t))));\n}\n```\n\n```javascript\npairs_second_attempt_example\n display_stream\n integers_definition\n\ndisplay_stream(pairs(integers, integers));\n\nstream_ref(pairs(integers, integers), 8);\n```\n\nWe began our discussion of streams by describing them as computational\nanalogs of the signals in signal-processing systems.\n\nIn fact, we can use streams to model signal-processing systems in a very\ndirect way, representing the values of a signal at successive time\nintervals as consecutive elements of a stream.\n\nFor instance, we can\nimplement an\nintegrator or\nsummer that, for an input stream\n$x=(x_{i})$ , an initial value $C$ , and a small increment $dt$ ,\naccumulates the sum\n\\[\n\\begin{array}{lll}\nS_i &=& C +\\sum_{j=1}^{i} x_{j} \\, dt\n\\end{array}\n\\]\nand returns the stream of values $S=(S_{i})$.\n\nThe following\nfunction\nis reminiscent of the implicit style definition of the\nstream of integers (section ):\n\n```javascript\nintegral_1_example\n\nfunction numbers_starting_from(t, dt) {\n return pair(t,\n () => numbers_starting_from(t + dt, dt)\n );\n}\nconst dt = 0.01;\nconst linear = numbers_starting_from(0, dt);\nconst linear_integral = integral(linear, 0, dt);\n// computing integral from 0 to 3 of f(x) = x\n// (the integral is g(x) = 0.5 x^2, and therefore\n// the result is near 0.5 * 3^2 = 4.5)\nstream_ref(linear_integral, math_round(3 / dt));\n```\n\n```javascript\nintegral_1\n add_streams\n scale_stream\n integral_1_example\n 4.484999999999992\n\nfunction integral(integrand, initial_value, dt) {\n const integ = pair(initial_value,\n () => add_streams(scale_stream(integrand, dt),\n integ));\n return integ;\n}\n```\n\nThe\n\n\\noindent\nFigure\nis a picture of a signal-processing\nsystem that corresponds to the\nfunction.\n\nThe input stream is scaled by $dt$ and passed\nthrough an adder, whose output is passed back through the same adder.", + "token_count": 290, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Exploiting the Stream Paradigm", + "chunk_index": 7, + "chunk_id": "Modularity_Objects_and_State_Exploiting_the_Stream_Paradigm_7" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_26", - "chunk_index": 26, - "content": "The following procedures define how the data-base index is used. A pattern (an assertion or a rule conclusion) will be stored in the table if it starts with a variable or a constant symbol. is_indexable variable (define (indexable? pat) (or (constant-symbol? (car pat)) (var? (car pat)))) The key under which a pattern is stored in the table is either ? (if it starts with a variable) or the constant symbol with which it starts. The key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with. index_key_of variable (define (index-key-of pat) (let ((key (car pat))) (if (var? key) '? key))) function index_key_of(pattern) { return head(pattern); } The index will be used to retrieve items that might match a pattern if the pattern starts with a constant symbol. use_index (define (use-index? pat) (constant-symbol? (car pat))) The query system uses a few stream operations that were not presented in chapter .", - "token_count": 224, - "source_files": [ - "subsection4.xml" - ] + "content": "The input stream is scaled by $dt$ and passed\nthrough an adder, whose output is passed back through the same adder.\n\nThe self-reference in the definition of\ninteg\nis reflected in the figure by the feedback loop that\nconnects the output of the adder to one of the inputs.", + "token_count": 49, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Exploiting the Stream Paradigm", + "chunk_index": 8, + "chunk_id": "Modularity_Objects_and_State_Exploiting_the_Stream_Paradigm_8" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_27", - "chunk_index": 27, - "content": "Stream-append-delayed The functions stream_append_delayed and interleave-delayed interleave_delayed are just like stream-append stream_append and interleave (section ), except that they take a delayed argument (like the integral procedure function in section ). This postpones looping in some cases (see exercise ). stream_append_delayed (define (stream-append-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (stream-append-delayed (stream-cdr s1) delayed-s2)))) (define (interleave-delayed s1 delayed-s2) (if (stream-null? s1) (force delayed-s2) (cons-stream (stream-car s1) (interleave-delayed (force delayed-s2) (delay (stream-cdr s1)))))) function stream_append_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => stream_append_delayed(stream_tail(s1), delayed_s2)); } function interleave_delayed(s1, delayed_s2) { return is_null(s1) ? delayed_s2() : pair(head(s1), () => interleave_delayed(delayed_s2(), () => stream_tail(s1))); }\nStream-flatmap , The function stream_flatmap , which is used throughout the query evaluator to map a procedure function over a stream of frames and combine the resulting streams of frames, is the stream analog of the flatmap procedure function introduced for ordinary lists in section . Unlike ordinary flatmap , however, we accumulate the streams with an interleaving process, rather than simply appending them (see exercises and ). stream_flatmap stream_append_delayed (define (stream-flatmap proc s) (flatten-stream (stream-map proc s))) (define (flatten-stream stream) (if (stream-null? stream) the-empty-stream (interleave-delayed (stream-car stream) (delay (flatten-stream (stream-cdr stream)))))) function stream_flatmap(fun, s) { return flatten_stream(stream_map(fun, s)); } function flatten_stream(stream) { return is_null(stream) ? null : interleave_delayed( head(stream), () => flatten_stream(stream_tail(stream))); }", - "token_count": 465, - "source_files": [ - "subsection4.xml" - ] + "content": "We have seen how to support the illusion of manipulating streams\nas complete entities even though, in actuality, we compute only\nas much of the stream as we need to access.\n\nWe can exploit this\ntechnique to represent sequences efficiently as streams, even if the\nsequences are very long.\n\nWhat is more striking, we can use streams to\nrepresent sequences that are infinitely long.\n\nFor instance, consider\nthe following definition of the stream of positive integers:\n\n```javascript\nintegers_starting_from_example\n\nconst from_20 = integers_starting_from(20);\neval_stream(from_20, 50);\n\nconst from_20 = integers_starting_from(20);\nstream_ref(from_20, 50);\n```\n\n```javascript\nintegers_starting_from\n integers_starting_from_example\n 70\n\nfunction integers_starting_from(n) {\n return pair(n, () => integers_starting_from(n + 1));\n}\n```\n\n```javascript\nintegers_definition\n integers_starting_from\n integers_definition_example\n 51\n\nconst integers = integers_starting_from(1);\n```\n\n```javascript\nintegers_definition_example\n\neval_stream(integers, 50);\n\nstream_ref(integers, 50);\n```\n\nThis makes sense because\nhead\nis 1 and whose\ntail\nis a promise to produce the integers beginning with 2.\n\nThis is an infinitely\nlong stream, but in any given time we can examine only a finite portion of\nit.\n\nThus, our programs will never know that the entire infinite stream is\nnot there.\n\nUsing\n\n```javascript\nis_divisible2_example\n\nis_divisible(42, 7);\n```\n\n```javascript\nis_divisible2\n is_divisible2_example\n\nfunction is_divisible(x, y) { return x % y === 0; }\n```\n\n```javascript\nno_sevens\n integers_definition\n is_divisible2\n no_sevens_example\n 27\n\nconst no_sevens = stream_filter(x => ! is_divisible(x, 7),\n integers);\n```\n\n```javascript\nno_sevens_example\n\neval_stream(no_sevens, 23);\n\nstream_ref(no_sevens, 23);\n```\n\nThen we can find integers not divisible by 7 simply by accessing elements of this stream:\n\n```javascript\non_sevens_example\n no_sevens\n 117\n\nstream_ref(no_sevens, 100);\n```\n\nIn analogy with\n\n```javascript\nfibgen_example\n\neval_stream(fibs, 50);\n\nstream_ref(fibs, 50);\n```\n\n```javascript\nfibgen\n fibgen_example\n 12586269025\n\nfunction fibgen(a, b) {\n return pair(a, () => fibgen(b, a + b));\n}\n\nconst fibs = fibgen(0, 1);\n```\n\nThe constant fibs\nis a pair whose\nhead\nis 0 and whose\ntail\nis a promise to evaluate\nfibgen(1, 1).", + "token_count": 298, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Infinite Streams", + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_Infinite_Streams_1" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_28", - "chunk_index": 28, - "content": "The evaluator also uses the following simple procedure function to generate a stream consisting of a single element: singleton_stream (define (singleton-stream x) (cons-stream x the-empty-stream)) function singleton_stream(x) { return pair(x, () => null); }\nWe saw in section that the driver loop first transforms an input string into the JavaScript syntax representation. The input is designed to look like a JavaScript expression so that we can use the parse function from section and also to support JavaScript notation in javascript_predicate . For example, parse('job($x, list(\"computer\", \"wizard\"));'); yields list(\"application\", list(\"name\", \"job\"), list(list(\"name\", \"$x\"), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The tag \"application\" indicates that syntactically, the query would be treated as a function application in JavaScipt. The function unparse transforms the syntax back into a string: unparse(parse('job($x, list(\"computer\", \"wizard\"));')); 'job($x, list(\"computer\", \"wizard\"))' In the query processor, we assumed a more appropriate, query-language-specific, query-language-specific representation of assertions, rules, and queries. The function convert_@to_@query_@syntax transforms the syntax representation into that representation.", - "token_count": 278, - "source_files": [ - "subsection4.xml" - ] + "content": "The constant fibs\nis a pair whose\nhead\nis 0 and whose\ntail\nis a promise to evaluate\nfibgen(1, 1).\n\nWhen we evaluate this delayed\nfibgen(1, 1),\nit will produce a pair whose\nhead\nis 1 and whose\ntail\nis a promise to evaluate\nfibgen(1, 2),\nand so on.\n\nFor a look at a more exciting infinite stream, we can generalize the no_sevens example to construct the infinite stream of prime numbers, using a\n\nmethod known as the sieve of Eratosthenes.\n\n```javascript\nsieve_example\n\neval_stream(primes, 50);\n\nstream_ref(primes, 50);\n```\n\n```javascript\nsieve\n is_divisible2\n integers_starting_from\n sieve_example\n 233\n\nfunction sieve(stream) {\n return pair(head(stream),\n () => sieve(stream_filter(\n x => ! is_divisible(x, head(stream)),\n stream_tail(stream))));\n}\nconst primes = sieve(integers_starting_from(2));\n```\n\nNow to find a particular prime we need only ask for it:\n\n```javascript\nsieve_example_2\n sieve\n 233\n\nstream_ref(primes, 50);\n```\n\nIt is interesting to contemplate the signal-processing system set up\nby Henderson diagram in\nfigure.\nunpairer\nthat separates the first element of the stream from the rest of the stream.\n\nThe first element is used to construct a divisibility filter, through\nwhich the rest is passed, and the output of the filter is fed to\nanother sieve box.\n\nThen the original first element is\n\n```javascript\nadjoined to the output of the internal sieve\n\tto form the output stream.\n```\n\nThus, not only is the stream infinite, but the signal processor is also infinite, because the sieve contains a sieve within it.\n\n```javascript\nThe prime sieve viewed as a signal-processing system.\nEach solid line represents a\n stream of values being transmitted. The dashed line from the\n head\n to the\n pair\n and the\n```\n\nThe generating\nfunctions\nthat explicitly compute the stream elements one by one.\n\nAn alternative way\nto specify streams is to take advantage of delayed evaluation to define\nstreams implicitly.\n\nFor example, the following\nstatement\ndefines the\nstream\n\n```javascript\nones_example\n\neval_stream(ones, 50);\n\nstream_ref(ones, 50);\n```", + "token_count": 309, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Infinite Streams", + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_Infinite_Streams_2" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_29", - "chunk_index": 29, - "content": "Using the same example, convert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));')); yields list(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\")) Query-system functions such as add_rule_or_assertion in section and evaluate_query in section operate on the query-language-specific representation using selectors and predicates such as type , contents , is_rule , and first_conjunct declared below. Figure depicts the three parse , unparse , and convert_to_query_syntax bridge them. Syntax abstraction in the query system. The predicate is_variable is used on the query-language-specific representation during query processing and on the JavaScript syntax representation during instantiation to identify names that start with a dollar sign. char_at that returns a string containing only the character of the given string at the given position. is_variable_2 function is_variable(exp) { return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\"; } const is_variable = is_name;\nUnique variables are constructed during rule application (in section ) by means of the following functions. The unique identifier for a rule application is a number, which is incremented each time a rule is applied.", - "token_count": 239, - "source_files": [ - "subsection4.xml" - ] + "content": "For example, the following\nstatement\ndefines the\nstream\n\n```javascript\nones_definition\n ones_example\n 1\n\nconst ones = pair(1, () => ones);\n```\n\nThis works much like the declaration of a recursive function: head is 1 and whose tail is a promise to evaluate tail gives us again\n\na 1 and a promise to evaluate\n\nWe can do more interesting things by manipulating streams with operations such as add_streams, which produces the elementwise sum of two given streams:\n\n```javascript\nadd_streams_example\n\nconst ones = pair(1, () => ones);\nconst twos = pair(2, () => twos);\nconst threes = add_streams(ones, twos);\neval_stream(threes, 50);\n\nconst ones = pair(1, () => ones);\nconst twos = pair(2, () => twos);\nconst threes = add_streams(ones, twos);\nstream_ref(threes, 50);\n```\n\n```javascript\nadd_streams\n stream_combine\n add_streams_example\n 3\n\nfunction add_streams(s1, s2) {\n return stream_map_2((x1, x2) => x1 + x2, s1, s2);\n}\n```\n\nNow we can define the integers as follows:\n\n```javascript\nintegers_definition_2_example\n\neval_stream(integers, 50);\n\nstream_ref(integers, 50);\n```\n\n```javascript\nintegers_definition_2\n add_streams\n ones_definition\n integers_definition_2_example\n 51\n\nconst integers = pair(1, () => add_streams(ones, integers));\n```\n\nThis defines\n\nWe can define the Fibonacci numbers in the same style:\n\n```javascript\nfibs_by_magic_example\n\neval_stream(fibs, 20);\n\nstream_ref(fibs, 20);\n```\n\n```javascript\nfibs_by_magic\n fibs_by_magic_example\n add_streams\n 6765\n\nconst fibs = pair(0,\n () => pair(1,\n () => add_streams(stream_tail(fibs),\n fibs)));\n```\n\nThis definition says that\n\n```javascript\n\\[\n\\begin{array}{ccccccccccccl}\n & & 1 & 1 & 2 & 3 & 5 & 8 & 13 & 21 & \\ldots & = & \\texttt{stream}\\mathtt{\\_}\\texttt{tail(fibs)} \\\\\n & & 0 & 1 & 1 & 2 & 3 & 5 & 8 & 13 & \\ldots & = & \\texttt{fibs} \\\\ \\hline\n0 & 1 & 1 & 2 & 3 & 5 & 8 & 13 & 21 & 34 & \\ldots & = & \\texttt{fibs}\n\\end{array}\n\\]\n```\n\n```javascript\nThe function\n\tscale_stream\n\tis also useful\n```\n\nin formulating such stream definitions.\n\nThis multiplies each item in a\nstream by a given constant:", + "token_count": 314, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Infinite Streams", + "chunk_index": 3, + "chunk_id": "Modularity_Objects_and_State_Infinite_Streams_3" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_30", - "chunk_index": 30, - "content": "is_variable_4 let rule_counter = 0; function new_rule_application_id() { rule_counter = rule_counter + 1; return rule_counter; } function make_new_variable(variable, rule_application_id) { return make_name(symbol_of_name(variable) + \"_\" + stringify(rule_application_id)); }\nThe function convert_to_query_syntax recursively \"pair\" or \"list\" , an (untagged) JavaScript pair or list is built. This means that convert_@to_@query_@syntax interprets applications of the constructors pair and list during the transformation, and processing functions such as pattern_match of section and unify_match of section can operate directly on the intended pairs and lists rather than on the syntax representation generated by the parser. The (one-element) argument list of javascript_predicate remains unprocessed, as explained below. A variable remains unchanged, and a literal is simplified to the primitive value it contains. convert_to_query_syntax functions_4_1_2 function convert_to_query_syntax(exp) { if (is_application(exp)) { const function_symbol = symbol_of_name(function_expression(exp)); if (function_symbol === \"javascript_predicate\") { return pair(function_symbol, arg_expressions(exp)); } else { const processed_args = map(convert_to_query_syntax, arg_expressions(exp)); return function_symbol === \"pair\" ? pair(head(processed_args), head(tail(processed_args))) : function_symbol === \"list\" ?", - "token_count": 287, - "source_files": [ - "subsection4.xml" - ] + "content": "This multiplies each item in a\nstream by a given constant:\n\n```javascript\nscale_stream_example\n\nconst twos = pair(2, () => twos);\nconst sixes = scale_stream(twos, 3);\neval_stream(sixes, 50);\n\nconst twos = pair(2, () => twos);\nconst sixes = scale_stream(twos, 3);\nstream_ref(sixes, 50);\n```\n\n```javascript\nscale_stream\n scale_stream_example\n 6\n\nfunction scale_stream(stream, factor) {\n return stream_map(x => x * factor,\n stream);\n}\n```\n\nFor example,\n\n```javascript\ndouble_stream_example\n\neval_stream(double, 50);\n\nstream_ref(double, 50);\n```\n\n```javascript\ndouble_stream\n scale_stream\n double_stream_example\n 1125899906842624\n\nconst double = pair(1, () => scale_stream(double, 2));\n```\n\nproduces the stream of powers of 2: $1, 2, 4, 8, 16, 32,$.\n\nAn alternate definition of the stream of primes can be given by\nstarting with the integers and filtering them by testing for\nprimality.\n\nWe will need the first prime, 2, to get started:\n\n```javascript\nprimes_example\n\neval_stream(primes, 50);\n\nstream_ref(primes, 50);\n```\n\n```javascript\nprimes\n square_definition\n is_divisible2\n integers_starting_from\n primes_example\n 233\n\nfunction is_prime(n) {\n function iter(ps) {\n return square(head(ps)) > n\n ? true\n : is_divisible(n, head(ps))\n ? false\n : iter(stream_tail(ps));\n }\n return iter(primes);\n}\n\nconst primes = pair(2,\n () => stream_filter(is_prime,\n integers_starting_from(3)));\n\nconst primes = pair(2,\n () => stream_filter(is_prime,\n integers_starting_from(3)));\n```\n\nThis definition is not so straightforward as it appears, because we will test whether a number $n$ is prime by checking whether $n$ is divisible\n\nby a prime (not by just any integer) less than or equal to $\\sqrt{n}$ :\n\n```javascript\nis_prime2_example\n\nis_prime(100003);\n```\n\n```javascript\nis_prime2\n square_definition\n is_divisible2\n integers_starting_from\n is_prime2_example\n true\n\nfunction is_prime(n) {\n function iter(ps) {\n return square(head(ps)) > n\n ? true\n : is_divisible(n, head(ps))\n ? false\n : iter(stream_tail(ps));\n }\n return iter(primes);\n}\n\nfunction is_prime(n) {\n function iter(ps) {\n return square(head(ps)) > n\n ? true\n : is_divisible(n, head(ps))\n ? false\n : iter(stream_tail(ps));\n }\n return iter(primes);\n}\nconst primes = pair(2,\n () => stream_filter(\n is_prime,\n integers_starting_from(3))\n );\n```\n\nThis is a recursive definition, since is_prime predicate, which itself uses the function works is that, at any point, enough of the $n$ we test", + "token_count": 315, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Infinite Streams", + "chunk_index": 4, + "chunk_id": "Modularity_Objects_and_State_Infinite_Streams_4" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_31", - "chunk_index": 31, - "content": "processed_args : pair(function_symbol, processed_args); } } else if (is_variable(exp)) { return exp; } else { // exp is literal return literal_value(exp); } }\nAn exception to this processing is javascript_predicate . Since the instantiated JavaScript syntax representation of its predicate expression is passed to evaluate of section , the original syntax representation coming from parse needs to remain intact in the query-language-specific representation of the expression. In this example of section and(salary($person, $amount), javascript_predicate($amount > 50000)) convert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation: list(\"and\", list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")), list(\"javascript_predicate\", list(\"binary_operator_combination\", \">\", list(\"name\", \"$amount\"), list(\"literal\", 50000)))) In order to evaluate the javascript_predicate subexpression of that processed query, the function javascript_@predicate in section calls the function instantiate_@expression (below) on the embedded JavaScript syntax representation of $amount > 50000 to replace the variable list(\"name\", \"$amount\") by a literal, for example list(\"literal\", 70000) , that represents the primitive value to which $amount is bound, here 70000. The JavaScript evaluator can evaluate the instantiated predicate, which now represents 70000 > 50000 .", - "token_count": 289, - "source_files": [ - "subsection4.xml" - ] + "content": "This is a recursive definition, since is_prime predicate, which itself uses the function works is that, at any point, enough of the $n$ we test\n\nfor primality, either $n$ is not prime (in which case there is a prime already generated that divides it) or $n$ is prime (in which\n\ncase there is a prime already generated i.e., a prime less than $n$ that is greater than $\\sqrt{n}$ ).", + "token_count": 69, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Infinite Streams", + "chunk_index": 5, + "chunk_id": "Modularity_Objects_and_State_Infinite_Streams_5" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_32", - "chunk_index": 32, - "content": "The function javascript_predicate of section and the driver loop of section call instantiate_@expression on an expression to obtain a copy in which any variable in the expression is replaced by its value in a given frame. The input and result expressions use the JavaScript syntax representation, so any value that results from instantiating a variable needs to be converted from its form in the binding to the JavaScript syntax representation. instantiate make_binding variable express convert function instantiate_expression(expression, frame) { return is_variable(expression) ? convert(instantiate_term(expression, frame)) : is_pair(expression) ? pair(instantiate_expression(head(expression), frame), instantiate_expression(tail(expression), frame)) : expression; } The function instantiate_term takes a variable, pair, or primitive value as first argument and a frame as second argument and recursively replaces the variables in the first argument by their values in the frame until a primitive value or an unbound variable is reached. When the process encounters a pair, a new pair is constructed whose parts are the instantiated versions of the original parts. For example, if $x is bound to the pair $[\\texttt{\\$y}, 5]$ in a frame $f$ as the result of unification, and $y is in turn bound to 3, the result of applying instantiate_term to list(\"name\", \"$x\") and $f$ is the pair $[3, 5]$ .", - "token_count": 281, - "source_files": [ - "subsection4.xml" - ] + "content": "As we saw in section , one of\nthe major benefits of introducing assignment is that we can increase the\nmodularity of our systems by encapsulating, or hiding, parts\nof the state of a large system within local variables.\n\nStream models can\nprovide an equivalent modularity without the use of assignment.\n\nAs an\nillustration, we can reimplement the\n$\\pi$ , which we examined in\nsection , from a\nstream-processing point of view.\n\nThe key modularity issue was that we wished to hide the internal state\nof a random-number generator from programs that used random numbers.\n\nWe began with a\nfunction rand_update,\nwhose successive values furnished our supply of random numbers, and used\nthis to produce a random-number generator:\n\n```javascript\nrand_update\n random_init\n rand_example\n 40083849805\n\nfunction make_rand() {\n let x = random_init;\n return () => {\n x = rand_update(x);\n return x;\n };\n}\nconst rand = make_rand();\n```\n\nIn the stream formulation there is no random-number generator per se , just a stream of random numbers produced by successive calls to rand_update:\n\n```javascript\nrandom_numbers\n rand_update\n random_init\n random_numbers_example\n 172561279022\n\nconst random_numbers =\n pair(random_init,\n () => stream_map(rand_update, random_numbers));\n```\n\n```javascript\nrandom_numbers_example\n\neval_stream(random_numbers, 5);\n\nstream_ref(random_numbers, 4);\n```\n\nWe use this to construct the stream of outcomes of the Ces ro experiment performed on consecutive pairs in the stream:\n\n```javascript\ncesaro_stream\n random_numbers\n gcd_definition\n cesaro_stream_example\n true\n\nfunction map_successive_pairs(f, s) {\n return pair(f(head(s), head(stream_tail(s))),\n () => map_successive_pairs(\n f,\n stream_tail(stream_tail(s))));\n}\nconst dirichlet_stream =\n map_successive_pairs((r1, r2) => gcd(r1, r2) === 1,\n random_numbers);\n```\n\n```javascript\ncesaro_stream_example\n\neval_stream(dirichlet_stream, 20);\n\nstream_ref(dirichlet_stream, 42);\n```\n\nThe\ndirichlet_stream\nis now fed to a\nmonte_carlo\nfunction,\nwhich produces a stream of estimates of probabilities.\n\nThe results are then\nconverted into a stream of estimates of $\\pi$.\n\nThis version of the program doesn t need a parameter telling how many\ntrials to perform.", + "token_count": 296, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Modularity of Functional Programs and Modularity of Objects", + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_Modularity_of_Functional_Programs_and_Modularity_of_Objects_1" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_33", - "chunk_index": 33, - "content": "express function instantiate_term(term, frame) { if (is_variable(term)) { const binding = binding_in_frame(term, frame); return is_undefined(binding) ? term // leave unbound variable as is : instantiate_term(binding_value(binding), frame); } else if (is_pair(term)) { return pair(instantiate_term(head(term), frame), instantiate_term(tail(term), frame)); } else { // $\\texttt{term}$ is a primitive value return term; } } The function convert constructs a JavaScript syntax representation for a variable, pair, or primitive value returned by instantiate_term . A pair in the original becomes an application of JavaScript's pair constructor and a primitive value becomes a literal. convert function convert(term) { return is_variable(term) ? term : is_pair(term) ?", - "token_count": 158, - "source_files": [ - "subsection4.xml" - ] + "content": "This version of the program doesn t need a parameter telling how many\ntrials to perform.\n\nBetter estimates of $\\pi$\n(from performing more experiments) are obtained by looking farther into the\n\n```javascript\nmonte_carlo_stream\n cesaro_stream\n display_pi\n 3.1780497164141406\n\nfunction monte_carlo(experiment_stream, passed, failed) {\n function next(passed, failed) {\n return pair(passed / (passed + failed),\n () => monte_carlo(stream_tail(experiment_stream),\n passed, failed));\n }\n return head(experiment_stream)\n ? next(passed + 1, failed)\n : next(passed, failed + 1);\n}\nconst pi = stream_map(p => math_sqrt(6 / p),\n monte_carlo(dirichlet_stream, 0, 0));\n```\n\n```javascript\ndisplay_pi\n\nstream_ref(pi, 100);\n```\n\nThere is considerable\nmonte_carlo function\nthat can deal with arbitrary experiments.\n\nYet there is no assignment or\nlocal state.\n\nLet us now return to the issues of objects and state that were raised\nat the beginning of this chapter and examine them in a new light.\n\nWe\nintroduced assignment and mutable objects to provide a mechanism for\nmodular construction of programs that model systems with state.\n\nWe constructed computational objects with local state variables and used\nassignment to modify these variables.\n\nWe modeled the temporal behavior of\nthe objects in the world by the temporal behavior of the corresponding\ncomputational objects.\n\nNow we have seen that streams provide an alternative way to model\nobjects with local state.\n\nWe can model a changing quantity, such as\nthe local state of some object, using a stream that represents the\ntime history of successive states.\n\nIn essence, we represent time\nexplicitly, using streams, so that we decouple time in our simulated\nworld from the sequence of events that take place during evaluation.\n\nIndeed, because of the presence of\ndelayed evaluation\nthere may be little relation between simulated time in the model and the\norder of events during the evaluation.", + "token_count": 283, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Modularity of Functional Programs and Modularity of Objects", + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_Modularity_of_Functional_Programs_and_Modularity_of_Objects_2" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_34", - "chunk_index": 34, - "content": "make_application(make_name(\"pair\"), list(convert(head(term)), convert(tail(term)))) : // $\\texttt{term}$ is a primitive value make_literal(term); } append_to_form process_query(`assert( rule(append_to_form(null, $y, $y)))`); process_query(`assert( rule(append_to_form(pair($u, $v), $y, pair($u, $z)), append_to_form($v, $y, $z)))`); process_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`); To illustrate these three functions, consider what happens when the query job($x, list(\"computer\", \"wizard\")) whose JavaScript syntax representation is given at the beginning of section , is processed by the driver loop. Let's say a frame $g$ of the result stream binds the variable $x to the pair $[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and the variable $y to the pair $[\\texttt{\"Ben\"}, \\texttt{null}]$ .", - "token_count": 228, - "source_files": [ - "subsection4.xml" - ] + "content": "Indeed, because of the presence of\ndelayed evaluation\nthere may be little relation between simulated time in the model and the\norder of events during the evaluation.\n\nIn order to contrast these two approaches to modeling, let us reconsider the implementation of a withdrawal processor that monitors the balance in a we\n\nimplemented a simplified version of such a processor:\n\n```javascript\nmake_simplified_withdraw_example\n make_simplified_withdraw_example1\n make_simplified_withdraw_example2\n\nfunction make_simplified_withdraw(balance) {\n return amount => {\n balance = balance - amount;\n return balance;\n };\n}\n```\n\nCalls to produce computational objects, each with a local state variable\n\nAlternatively, we can model a withdrawal processor as a function that takes as input a balance and a stream of amounts to withdraw and produces\n\nthe stream of successive balances in the account:\n\n```javascript\nstream_withdraw\n stream_withdraw_example\n 50\n\nfunction stream_withdraw(balance, amount_stream) {\n return pair(balance,\n () => stream_withdraw(balance - head(amount_stream),\n stream_tail(amount_stream)));\n}\n```\n\n```javascript\nstream_withdraw_example\n\nconst my_amounts = list_to_stream(list(50, 100, 40));\nconst my_account_stream = stream_withdraw(200, my_amounts);\neval_stream(my_account_stream, 3);\n\nconst my_amounts = list_to_stream(list(50, 100, 40));\nconst my_account_stream = stream_withdraw(200, my_amounts);\nstream_ref(my_account_stream, 2);\n```\n\nThe function stream_withdraw\nimplements a well-defined mathematical function whose output is fully\ndetermined by its input.\n\nSuppose, however, that the input\namount_stream\nis the stream of successive values typed by the user and that the resulting\nstream of balances is displayed.\n\nThen, from the perspective of the user who\nis typing values and watching results, the stream process has the same\nbehavior as the object created by\nmake_simplified_withdraw.\n\nHowever, with the stream version, there is no assignment, no local state\nvariable, and consequently none of the theoretical difficulties that we\nencountered.\n\nYet the system\nhas state!\n\nThis is really remarkable.\n\nEven though\nstream_withdraw\nimplements a well-defined mathematical function whose behavior does not\nchange, the user s perception here is one of interacting with a system\nthat has a changing state.", + "token_count": 302, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Modularity of Functional Programs and Modularity of Objects", + "chunk_index": 3, + "chunk_id": "Modularity_Objects_and_State_Modularity_of_Functional_Programs_and_Modularity_of_Objects_3" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_35", - "chunk_index": 35, - "content": "Then instantiate_term(list(\"name\", \"$\\$$x\"), $g$) returns the list list(\"Bitdiddle\", \"Ben\") which convert transforms into list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))) The result of instantiate_expression applied to the JavaScript syntax representation of the query and the frame $g$ is: list(\"application\", list(\"name\", \"job\"), list(list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Bitdiddle\"), list(\"application\", list(\"name\", \"pair\"), list(list(\"literal\", \"Ben\"), list(\"literal\", null))))), list(\"application\", list(\"name\", \"list\"), list(list(\"literal\", \"computer\"), list(\"literal\", \"wizard\"))))) The driver loop unparses this representation and displays it as: 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'\nThe function unparse transforms a component given in the JavaScript syntax representation into a string by applying the syntax rules of section . We describe unparse only for those kinds of expressions that appear in the examples of section , leaving statements and the remaining kinds of expressions as exercise .", - "token_count": 272, - "source_files": [ - "subsection4.xml" - ] + "content": "Even though\nstream_withdraw\nimplements a well-defined mathematical function whose behavior does not\nchange, the user s perception here is one of interacting with a system\nthat has a changing state.\n\nOne way to resolve this paradox is to realize\nthat it is the user s temporal existence that imposes state on the\nsystem.\n\nIf the user could step back from the interaction and think in terms\nof streams of balances rather than individual transactions, the system\nwould appear stateless.\n\nFrom the point of view of one part of a complex process, the other parts\nappear to change with time.\n\nThey have hidden time-varying local state.\n\nIf\nwe wish to write programs that model this kind of natural decomposition in\nour world (as we see it from our viewpoint as a part of that world) with\nstructures in our computer, we make computational objects that are not\nfunctional they must change with time.\n\nWe model state with local\nstate variables, and we model the changes of state with assignments to\nthose variables.\n\nBy doing this we make the time of execution of a\ncomputation model time in the world that we are part of, and thus we\nget objects in our computer.\n\nModeling with objects is powerful and intuitive, largely because this\nmatches the perception of interacting with a world of which we are\npart.\n\nHowever, as we ve seen repeatedly throughout this chapter,\nthese models raise thorny problems of constraining the order of events\nand of synchronizing multiple processes.\n\nThe possibility of avoiding\nthese problems has stimulated the development of\nfunctional programming languages , which do not include any\nprovision for assignment or mutable data.\n\nIn such a language, all\nfunctions\nimplement well-defined mathematical functions of their arguments,\nwhose behavior does not change.", + "token_count": 293, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Modularity of Functional Programs and Modularity of Objects", + "chunk_index": 4, + "chunk_id": "Modularity_Objects_and_State_Modularity_of_Functional_Programs_and_Modularity_of_Objects_4" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_36", - "chunk_index": 36, - "content": "A literal is transformed by stringify ing its value, and a name is transformed into its unparse is_list_construction element_expressions comma_separated function unparse(exp) { return is_literal(exp) ? stringify(literal_value(exp)) : is_name(exp) ? symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" unparsing other kinds of JavaScript components : error(exp, \"unknown syntax -- unparse\"); } function has_char(x, c) { let found = false; let i = 0; while (char_at(x, i) !== undefined) { found = found || char_at(x, i) === c; i = i + 1; } return found; } function better_stringify(x) { return is_string(x) && ! has_char(x, \"'\") ? \"'\" + x + \"'\" : stringify(x); } function unparse(exp) { return is_literal(exp) ? better_stringify(literal_value(exp)) : is_name(exp) ?", - "token_count": 286, - "source_files": [ - "subsection4.xml" - ] + "content": "In such a language, all\nfunctions\nimplement well-defined mathematical functions of their arguments,\nwhose behavior does not change.\n\nThe functional approach is extremely\nattractive for dealing with\n\nOn the other hand, if we look closely, we can see time-related problems\ncreeping into functional models as well.\n\nOne particularly troublesome area\narises when we wish to design interactive systems, especially ones that\nmodel interactions between independent entities.\n\nFor instance, consider once\nmore the implementation of a banking system that permits joint bank accounts.\n\nIn a conventional system using assignment and objects, we would model the\nfact that Peter and Paul share an account by having both Peter and Paul send\ntheir transaction requests to the same bank-account object, as we saw in\nsection.\n\nFrom the stream point\nof view, where there are no objects per se , we have\nalready indicated that a bank account can be modeled as a process that\noperates on a stream of transaction requests to produce a stream of\nresponses.\n\nAccordingly, we could model the fact that Peter and Paul have a\njoint bank account by merging Peter s stream of transaction requests\nwith Paul s stream of requests and feeding the result to the\nbank-account stream process, as shown in\nfigure.\n\nA joint\n\nThe trouble with this formulation is in the notion of merge.\n\nIt\nwill not do to merge the two streams by simply taking alternately one\nrequest from Peter and one request from Paul.\n\nSuppose Paul accesses\nthe account only very rarely.", + "token_count": 249, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Modularity of Functional Programs and Modularity of Objects", + "chunk_index": 5, + "chunk_id": "Modularity_Objects_and_State_Modularity_of_Functional_Programs_and_Modularity_of_Objects_5" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_37", - "chunk_index": 37, - "content": "symbol_of_name(exp) : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) : is_application(exp) && is_name(function_expression(exp)) ? symbol_of_name(function_expression(exp)) + \"(\" + comma_separated(map(unparse, arg_expressions(exp))) + \")\" : is_binary_operator_combination(exp) ? \"(\" + unparse(first_operand(exp)) + \" \" + operator_symbol(exp) + \" \" + unparse(second_operand(exp)) + \")\" : error(exp, \"unknown syntax -- unparse\"); } comma_separated function comma_separated(strings) { return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc), \"\", strings); } The function unparse would work fine without the clause : is_list_construction(exp) ? unparse(make_application(make_name(\"list\"), element_expressions(exp))) but the output string would be unnecessarily verbose in cases where pattern variables are instantiated by lists.", - "token_count": 199, - "source_files": [ - "subsection4.xml" - ] + "content": "Suppose Paul accesses\nthe account only very rarely.\n\nWe could hardly force Peter to wait for\nPaul to access the account before he could issue a second transaction.\nreal\ntime as perceived by Peter and Paul, in the sense that, if Peter and\nPaul meet, they can agree that certain transactions were processed\nbefore the meeting, and other transactions were processed after the\nmeeting. , where we found the need to\nintroduce explicit synchronization to ensure a correct order\nof events in concurrent processing of objects with state.\n\nThus, in an\nattempt to support the functional style, the need to merge inputs from\ndifferent agents reintroduces the same problems that the functional style\nwas meant to eliminate.\n\nWe began this chapter with the goal of building computational models\nwhose structure matches our perception of the real world we are trying\nto model.\n\nWe can model the world as a collection of separate,\ntime-bound, interacting objects with state, or we can model the world\nas a single, timeless, stateless unity.\n\nEach view has powerful\nadvantages, but neither view alone is completely satisfactory.\n\nA\ngrand unification has yet to emerge.", + "token_count": 188, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Modularity of Functional Programs and Modularity of Objects", + "chunk_index": 6, + "chunk_id": "Modularity_Objects_and_State_Modularity_of_Functional_Programs_and_Modularity_of_Objects_6" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_38", - "chunk_index": 38, - "content": "In the example above, where processing the query job($x, list(\"computer\", \"wizard\")) yields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces 'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))' However, without the clause it would produce 'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))' which explicitly constructs the two pairs that make up the first list. To achieve the more concise formatting used throughout section , we inserted the clause to check if the expression constructs a list, in which case we format it as a single application of list to the list of element expressions that we extract from the expression. A list construction is the literal null or an application of pair whose second argument is itself a list construction. is_list_construction function is_list_construction(exp) { return (is_literal(exp) && is_null(literal_value(exp))) || (is_application(exp) && is_name(function_expression(exp)) && symbol_of_name(function_expression(exp)) === \"pair\" && is_list_construction(head(tail(arg_expressions(exp))))); } Extracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached. element_expressions function element_expressions(list_constr) { return is_literal(list_constr) ?", - "token_count": 299, - "source_files": [ - "subsection4.xml" - ] + "content": "As we saw in\nsection ,\nsequences can serve as standard interfaces for combining program\nmodules.\n\nWe formulated powerful abstractions for manipulating\nsequences, such as\n\nUnfortunately, if we represent sequences as lists, this elegance is\nbought at the price of severe inefficiency with respect to both the\ntime and space required by our computations.\n\nWhen we represent manipulations on sequences as transformations\nof lists, our programs must construct and copy data structures (which\nmay be huge) at every step of a process.\n\nTo see why this is true, let us compare two programs for computing the\nsum of all the prime numbers in an interval.\n\nThe first program is\nwritten in standard iterative style:\n\n```javascript\nprime_definition\n sum_primes1\n sum_primes1_example\n 3437\n\nfunction sum_primes(a, b) {\n function iter(count, accum) {\n return count > b\n ? accum\n : is_prime(count)\n ? iter(count + 1, count + accum)\n : iter(count + 1, accum);\n }\n return iter(a, 0);\n}\n```\n\n```javascript\nsum_primes1_example\n\nsum_primes(7, 182);\n```\n\nThe second program performs the same computation using the sequence operations of section :\n\n```javascript\nsum_primes2\n prime_definition\n enumerate_interval\n sum_primes1_example\n 3437\n\nfunction sum_primes(a, b) {\n return accumulate((x, y) => x + y,\n 0,\n filter(is_prime,\n enumerate_interval(a, b)));\n}\n```\n\nIn carrying out the computation, the first program needs to store only\nthe sum being accumulated.\n\nIn contrast, the filter in the second\nprogram cannot do any testing until\nenumerate_interval\nhas constructed a complete list of the numbers in the interval.\n\nThe filter generates another list, which in turn is passed to\n\nThe inefficiency in using lists becomes painfully apparent if we use the sequence paradigm to compute the second prime in the interval from 10,000 to\n\n1,000,000 by evaluating the expression\n\n```javascript\npainfully\n prime_definition\n enumerate_interval\n\nhead(tail(filter(is_prime,\n enumerate_interval(10000, 1000000))));\n```\n\nThis expression does find the second prime, but the computational overhead\nis outrageous.", + "token_count": 298, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Streams Are Delayed Lists", + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_Streams_Are_Delayed_Lists_1" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_39", - "chunk_index": 39, - "content": "null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$ : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$ pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); } function element_expressions(list_constr) { return is_literal(list_constr) ? null // list_constr is literal null : // list_constr is application of pair pair(head(arg_expressions(list_constr)), element_expressions( head(tail(arg_expressions(list_constr))))); }\nThe functions type and contents , used by evaluate_query (section ), specify that a syntactic form of a query-language-specific representation is identified by the string in its head. They are the same as the type_tag and contents functions in section , except for the error message. type functions_4_1_2 function type(exp) { return is_pair(exp) ? head(exp) : error(exp, \"unknown expression type\"); } function contents(exp) { return is_pair(exp) ?", - "token_count": 227, - "source_files": [ - "subsection4.xml" - ] + "content": "This expression does find the second prime, but the computational overhead\nis outrageous.\n\nWe construct a list of almost a million integers, filter\nthis list by testing each element for primality, and then ignore almost\nall of the result.\n\nIn a more traditional programming style, we would\ninterleave the enumeration and the filtering, and stop when we reached\nthe second prime.\n\nStreams are a clever idea that allows one to use sequence\nmanipulations without incurring the costs of manipulating sequences as\nlists.\n\nWith streams we can achieve the best of both worlds: We can\nformulate programs elegantly as sequence manipulations, while attaining\nthe efficiency of incremental computation.\n\nThe basic idea is to arrange\nto construct a stream only partially, and to pass the partial\nconstruction to the program that consumes the stream.\n\nIf the consumer\nattempts to access a part of the stream that has not yet been\nconstructed, the stream will automatically construct just enough more\nof itself to produce the required part, thus preserving the illusion\nthat the entire stream exists.\n\nIn other words, although we will write\nprograms as if we were processing complete sequences, we design our\nstream implementation to automatically and transparently interleave\nthe construction of the stream with its use.\n\nTo accomplish this, we will construct streams using pairs,\nwith the first item of the stream in the head of the pair.\n\nHowever, rather than placing the value of the rest of the stream\npromise\nto compute the rest if it is ever requested.", + "token_count": 250, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Streams Are Delayed Lists", + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_Streams_Are_Delayed_Lists_2" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_40", - "chunk_index": 40, - "content": "tail(exp) : error(exp, \"unknown expression contents\"); }\nThe following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which the function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] : is_assertion type function is_assertion(exp) { return type(exp) === \"assert\"; } function assertion_body(exp) { return head(contents(exp)); }", - "token_count": 99, - "source_files": [ - "subsection4.xml" - ] + "content": "However, rather than placing the value of the rest of the stream\npromise\nto compute the rest if it is ever requested.\n\nIf we have a data item\nh and a stream\nt , we construct a stream\nwhose head is\nh and whose tail is\nt by evaluating\npair(h, () => t) the\ntail\nt of a stream is\nwrapped in a function of no arguments,\ndelayed.\nnull , the same as the empty list.\n\nTo access the first data item of a nonempty stream,\nwe simply select the\nhead of the pair, as with a list.\n\nBut to access the tail of a stream, we need to evaluate the\ndelayed expression.\n\nFor convenience, we define\n\n```javascript\nstream_tail\n\t stream_tail_example\n\t 5\n\nfunction stream_tail(stream) {\n return tail(stream)();\n}\n```\n\n```javascript\nstream_tail_example\n\nstream_tail(pair(4, () => pair(5, () => null)));\n\nhead(stream_tail(pair(4, () => pair(5, () => null))));\n```\n\nThis selects the tail of the pair and applies the function found there to obtain the next pair of the stream (or null if the\n\ntail of the stream is empty) in effect, forcing the function in the tail of the pair to fulfill its promise.\n\nWe can make and use streams, in just the same way as we can make\nand use lists, to represent aggregate data arranged in a sequence.\n\nIn\nparticular, we can build stream analogs of the list operations from\nchapter , such as list_ref ,\nfor_each :\n\n```javascript\nstream_tail\n stream_functions\n stream_functions_example\n\nfunction stream_ref(s, n) {\n return n === 0\n ? head(s)\n : stream_ref(stream_tail(s), n - 1);\n}\nfunction stream_map(f, s) {\n return is_null(s)\n ? null\n : pair(f(head(s)),\n () => stream_map(f, stream_tail(s)));\n}\nfunction stream_for_each(fun, s) {\n if (is_null(s)) {\n return true;\n } else {\n fun(head(s));\n return stream_for_each(fun, stream_tail(s));\n }\n}\n```", + "token_count": 291, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Streams Are Delayed Lists", + "chunk_index": 3, + "chunk_id": "Modularity_Objects_and_State_Streams_Are_Delayed_Lists_3" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_41", - "chunk_index": 41, - "content": "Here are the declarations of the predicates and selectors for the and , or , not , and javascript_predicate syntactic forms (section ): is_empty_conjunction function is_empty_conjunction(exps) { return is_null(exps); } function first_conjunct(exps) { return head(exps); } function rest_conjuncts(exps) { return tail(exps); } function is_empty_disjunction(exps) { return is_null(exps); } function first_disjunct(exps) { return head(exps); } function rest_disjuncts(exps) { return tail(exps); } function negated_query(exps) { return head(exps); } function javascript_predicate_expression(exps) { return head(exps); }\nThe following three functions define the query-language-specific representation of rules: is_rule functions_4_1_2 function is_rule(assertion) { return is_tagged_list(assertion, \"rule\"); } function conclusion(rule) { return head(tail(rule)); } function rule_body(rule) { return is_null(tail(tail(rule))) ? list(\"always_true\") : head(tail(tail(rule))); }\nType and contents , used by qeval (section ), specify that a special form is identified by the symbol in its car . They are the same as the type-tag and contents procedures in section , except for the error message. type_scheme functions_4_1_2 (define (type exp) (if (pair? exp) (car exp) (error \"Unknown expression TYPE\" exp))) (define (contents exp) (if (pair? exp) (cdr exp) (error \"Unknown expression CONTENTS\" exp)))", - "token_count": 345, - "source_files": [ - "subsection4.xml" - ] + "content": "In\nparticular, we can build stream analogs of the list operations from\nchapter , such as list_ref ,\nfor_each :\n\n```javascript\nstream_tail\n stream_functions_example\n\nconst my_stream = pair(4, () => pair(5, () => null));\ndisplay(stream_ref(my_stream, 1));\nconst my_stream_2 = stream_map(x => x + 1, my_stream);\nstream_for_each(display, my_stream_2);\n\nconst my_stream = pair(4, () => pair(5, () => null));\nconst my_stream_2 = stream_map(x => x + 1, my_stream);\nlet acc = 0;\nstream_for_each(x => {acc = acc + x;}, my_stream_2);\nacc;\n```\n\nThe function stream_for_each is useful for viewing streams:\n\n```javascript\ndisplay_stream\n display_stream_example\n\nfunction display_stream(s) {\n return stream_for_each(display, s);\n}\n\nconst max_display = 9;\nfunction display_stream(s) {\n function display_stream_iter(st, n) {\n if (is_null(st)) {\n } else if (n === 0) {\n display('', \"...\");\n } else {\n display(head(st));\n display_stream_iter(stream_tail(st), n - 1);\n }\n }\n display_stream_iter(s, max_display);\n}\n```\n\n```javascript\nstream_tail\n\t display_stream\n display_stream_example\n\nconst my_stream = pair(4, () => pair(5, () => null));\ndisplay_stream(my_stream);\n```\n\nTo make the stream implementation automatically and transparently\ninterleave the construction of a stream with its use, we have arranged\nfor the tail\nof a stream to be evaluated when it is accessed by the\nstream_tail\nfunction rather than when the stream is constructed by\npair.\n\nThis implementation choice is reminiscent of our discussion of rational numbers\nin section , where we saw\nthat we can choose to implement rational numbers so that the reduction\nof numerator and denominator to lowest terms is performed either at\nconstruction time or at selection time.\n\nThe two rational-number\nimplementations produce the same data abstraction, but the choice has\nan effect on efficiency.\n\nThere is a similar relationship between\nstreams and ordinary lists.\n\nAs a data abstraction, streams are the\nsame as lists.\n\nThe difference is the time at which the elements are\nevaluated.\n\nWith ordinary lists, both the\nhead and the\ntail\nare evaluated at construction time.", + "token_count": 304, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Streams Are Delayed Lists", + "chunk_index": 4, + "chunk_id": "Modularity_Objects_and_State_Streams_Are_Delayed_Lists_4" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_42", - "chunk_index": 42, - "content": "The following procedures, used by query-driver-loop (in section ), specify that rules and assertions are added to the data base by expressions of the form (assert! rule-or-assertion) : is_assertion_scheme type (define (assertion-to-be-added? exp) (eq? (type exp) 'assert!)) (define (add-assertion-body exp) (car (contents exp)))", - "token_count": 83, - "source_files": [ - "subsection4.xml" - ] + "content": "With ordinary lists, both the\nhead and the\ntail\nare evaluated at construction time.\n\nWith streams, the\ntail is evaluated at selection time.\n\nTo see how this data structure behaves, let us analyze the outrageous prime computation we saw above, reformulated in terms of streams:\n\n```javascript\nno_more_outrageous\n prime_definition\n stream_enumerate_interval\n\t 10009\n\nhead(stream_tail(stream_filter(\n is_prime,\n stream_enumerate_interval(10000, 1000000))));\n```\n\nWe will see that it does indeed work efficiently.\n\nWe begin by calling\nstream_enumerate_interval with\nthe arguments 10,000 and 1,000,000.\n\nThe function\nstream_enumerate_interval\nis the stream analog of\nenumerate_interval\n(section ):\n\n```javascript\nstream_enumerate_interval_example\n stream_enumerate_interval\n\nstream_enumerate_interval(10000, 1000000);\n\nstream_ref(stream_enumerate_interval(10000, 1000000), 100);\n```\n\n```javascript\nprime_definition\n stream_enumerate_interval\n\t stream_enumerate_interval_example\n\t 10100\n\nfunction stream_enumerate_interval(low, high) {\n return low > high\n ? null\n : pair(low,\n () => stream_enumerate_interval(low + 1, high));\n}\n```\n\nand thus the result returned by stream_enumerate_interval , formed by the\n\n```javascript\nstream_enumerate_interval_example_2\n stream_enumerate_interval\n\t 10100\n\npair(10000, () => stream_enumerate_interval(10001, 1000000));\n\nstream_ref(pair(10000, () => stream_enumerate_interval(10001, 1000000)), 100);\n```\n\nThat is, stream_enumerate_interval\nreturns a stream represented as a pair whose\nhead\nis 10,000 and whose tail\nis a promise to enumerate more of the\ninterval if so requested.\n\nThis stream is now filtered for primes,\nusing the stream analog of the ):\n\n```javascript\nstream_tail\n stream_filter\n stream_filter_example\n\t 6\n\nfunction stream_filter(pred, stream) {\n return is_null(stream)\n ? null\n : pred(head(stream))\n ? pair(head(stream),\n () => stream_filter(pred, stream_tail(stream)))\n : stream_filter(pred, stream_tail(stream));\n}\n```\n\n```javascript\nstream_filter_example\n display_stream\n\nconst my_stream = pair(5, () => pair(6, () => pair(7, () => null)));\nconst my_filtered_stream =\n stream_filter(x => x % 2 === 0, my_stream);\ndisplay_stream(my_filtered_stream);\n\nconst my_stream = pair(5, () => pair(6, () => pair(7, () => null)));\nconst my_filtered_stream =\n stream_filter(x => x % 2 === 0, my_stream);\nhead(my_filtered_stream);\n```\n\nThe function stream_filter tests the\nstream_filter\nexamines the tail of its input stream.\n\nThe call to\nstream_tail forces evaluation of the\ndelayed stream_enumerate_interval ,\nwhich now returns\n\n```javascript\nenumerate_interval_example_3\n stream_enumerate_interval\n\t 10101\n\npair(10001, () => stream_enumerate_interval(10002, 1000000));\n\nstream_ref(pair(10001, () => stream_enumerate_interval(10002, 1000000)), 100);\n```", + "token_count": 313, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Streams Are Delayed Lists", + "chunk_index": 5, + "chunk_id": "Modularity_Objects_and_State_Streams_Are_Delayed_Lists_5" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_43", - "chunk_index": 43, - "content": "Here are the syntax definitions for the and , or , not , and lisp-value special forms (section ): is_empty_conjunction_scheme (define (empty-conjunction? exps) (null? exps)) (define (first-conjunct exps) (car exps)) (define (rest-conjuncts exps) (cdr exps)) (define (empty-disjunction? exps) (null? exps)) (define (first-disjunct exps) (car exps)) (define (rest-disjuncts exps) (cdr exps)) (define (negated-query exps) (car exps)) (define (predicate exps) (car exps)) (define (args exps) (cdr exps))\nThe following three procedures define the syntax of rules: is_rule_scheme functions_4_1_2 (define (rule? statement) (tagged-list? statement 'rule)) (define (conclusion rule) (cadr rule)) (define (rule-body rule) (if (null? (cddr rule)) '(always-true) (caddr rule)))\nQuery-driver-loop (section ) calls query-syntax-process to transform pattern variables in the expression, which have the form ?symbol , into the internal format (? symbol) . That is to say, a pattern such as (job ?x ?y) is actually represented internally by the system as (job (? x) (? y)) . This increases the efficiency of query processing, since it means that the system can check to see if an expression is a pattern variable by checking whether the car of the expression is the symbol ? , rather than having to extract characters from the symbol. The syntax transformation is accomplished by the following procedure: query_process_scheme (define (query-syntax-process exp) (map-over-symbols expand-question-mark exp)) (define (map-over-symbols proc exp) (cond ((pair? exp) (cons (map-over-symbols proc (car exp)) (map-over-symbols proc (cdr exp)))) ((symbol? exp) (proc exp)) (else exp))) (define (expand-question-mark symbol) (let ((chars (symbol->string symbol))) (if (string=? (substring chars 0 1) \"?\") (list '? (string->symbol (substring chars 1 (string-length chars)))) symbol)))", - "token_count": 486, - "source_files": [ - "subsection4.xml" - ] + "content": "The call to\nstream_tail forces evaluation of the\ndelayed stream_enumerate_interval ,\nwhich now returns\n\nThe function stream_filter now looks at the head of this stream, 10,001, sees that this is not prime either, forces another stream_@tail , and so\n\non, until stream_@enumerate_interval yields the prime 10,007, whereupon stream_@filter , according to its definition, returns\n\n```javascript\npair(head(stream),\n () => stream_filter(pred, stream_tail(stream)));\n```\n\nwhich in this case is\n\n```javascript\nwhich_in_this_case\n prime_definition\n stream_enumerate_interval\n\t 10949\n\npair(10007,\n () => stream_filter(\n is_prime,\n pair(10008,\n () => stream_enumerate_interval(10009, 1000000))));\n\nstream_ref(pair(10007,\n () => stream_filter(\n is_prime,\n pair(10008,\n () => stream_enumerate_interval(10009, 1000000))\n )\n ), 100);\n```\n\nThis result is now passed to\nstream_tail in our original\nexpression.\n\nThis forces the delayed\nstream_filter ,\nwhich in turn keeps forcing the delayed\nstream_@enumerate_interval until it\nfinds the next prime, which is 10,009.\n\nFinally, the result passed to\n\n```javascript\nnow_passed_to\n prime_definition\n stream_enumerate_interval\n\t 10957\n\npair(10009,\n () => stream_filter(\n is_prime,\n pair(10010,\n () => stream_enumerate_interval(10011, 1000000))));\n\nstream_ref(pair(10009,\n () => stream_filter(\n is_prime,\n pair(10010,\n () => stream_enumerate_interval(10011, 1000000))\n )\n ), 100);\n```\n\nThe function\n\nIn general, we can think of delayed evaluation as\ndemand-driven programming, whereby each stage in the\nstream process is activated only enough to satisfy the next stage.\n\nWhat\nwe have done is to\nall at once when, in reality, the computation is\nperformed incrementally, as in traditional programming styles.\n\nWhen we construct stream pairs, we delay the evaluation of their tail\nexpressions by wrapping these expressions in a function.\n\nWe force their\nevaluation when needed, by applying the function.\n\nThis implementation suffices for streams to work as advertised, but\nthere is an important optimization that we shall consider where needed.\n\nIn many applications, we end up forcing the same delayed object many\ntimes.\n\nThis can lead to serious inefficiency in recursive programs\ninvolving streams.", + "token_count": 289, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Streams Are Delayed Lists", + "chunk_index": 6, + "chunk_id": "Modularity_Objects_and_State_Streams_Are_Delayed_Lists_6" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_44", - "chunk_index": 44, - "content": "Once the variables are transformed in this way, the variables in a pattern are lists starting with ? , and the constant symbols (which need to be recognized for data-base indexing, section ) are just the symbols. is_var_scheme functions_4_1_2 (define (var? exp) (tagged-list? exp '?)) (define (constant-symbol? exp) (symbol? exp))\nUnique variables are constructed during rule application (in section ) by means of the following procedures. The unique identifier for a rule application is a number, which is incremented each time a rule is applied. new_rule_application_id (define rule-counter 0) (define (new-rule-application-id) (set! rule-counter (+ 1 rule-counter)) rule-counter) (define (make-new-variable var rule-application-id) (cons '? (cons rule-application-id (cdr var))))", - "token_count": 177, - "source_files": [ - "subsection4.xml" - ] + "content": "This can lead to serious inefficiency in recursive programs\ninvolving streams.\n\n(See\nexercise.)\nThe solution is to build delayed objects so that the first time they are\nforced, they store the value that is computed.\n\nSubsequent forcings will\nsimply return the stored value without repeating the computation.\n\nIn\nother words, we implement the construction of stream pairs as a.\n\nOne way to accomplish this\nis to use the following function, which takes as argument a function\n(of no arguments) and returns a memoized version of the function.\n\nThe first time the memoized function is run, it saves the computed\nresult.\n\nOn subsequent evaluations, it simply returns\nthe result.\n\n```javascript\nmemo\n\t memo_example\n\t 1\n\nfunction memo(fun) {\n let already_run = false;\n let result = undefined;\n return () => {\n if (!already_run) {\n result = fun();\n already_run = true;\n return result;\n } else {\n return result;\n }\n };\n}\n```\n\n```javascript\nmemo_example\n\nfunction square_4() {\n const result = 4 * 4;\n display(\"multiplication carried out\");\n return result;\n}\nconst memo_square_4 = memo(square_4);\ndisplay(memo_square_4()); // shows \"multipl..\"\ndisplay(memo_square_4()); // does not show \"multipl..\"\n\nlet calls = 0;\nfunction square_4() {\n const result = 4 * 4;\n calls = calls + 1;\n return result;\n}\nconst memo_square_4 = memo(square_4);\nmemo_square_4();\nmemo_square_4();\ncalls;\n```\n\nWe can make use of memo whenever\nwe construct a stream pair.\n\nFor example, instead of\n\n```javascript\nstream_map_example\n\t 3\n\nfunction stream_map(f, s) {\n return is_null(s)\n ? null\n : pair(f(head(s)),\n () => stream_map(f, stream_tail(s)));\n}\n```", + "token_count": 242, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Streams Are Delayed Lists", + "chunk_index": 7, + "chunk_id": "Modularity_Objects_and_State_Streams_Are_Delayed_Lists_7" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Logic Programming", - "subsection": "Implementing the Query System", - "chunk_id": "chapter4_chunks_Implementing_the_Query_System_45", - "chunk_index": 45, - "content": "When query-driver-loop instantiates the query to print the answer, it converts any unbound pattern variables back to the right form for printing, using contract_question_mark_scheme (define (contract-question-mark variable) (string->symbol (string-append \"?\" (if (number? (cadr variable)) (string-append (symbol->string (caddr variable)) \"-\" (number->string (cadr variable))) (symbol->string (cadr variable))))))\nFrames are represented as lists of bindings, which are variable-value pairs: make_binding operation_table_from_chapter_3 operation_table (define (make-binding variable value) (cons variable value)) (define (binding-variable binding) (car binding)) (define (binding-value binding) (cdr binding)) (define (binding-in-frame variable frame) (assoc variable frame)) (define (extend variable value frame) (cons (make-binding variable value) frame)) function make_binding(variable, value) { return pair(variable, value); } function binding_variable(binding) { return head(binding); } function binding_value(binding) { return tail(binding); } function binding_in_frame(variable, frame) { return assoc(variable, frame); } function extend(variable, value, frame) { return pair(make_binding(variable, value), frame); }", - "token_count": 257, - "source_files": [ - "subsection4.xml" - ] + "content": "For example, instead of\n\n```javascript\nstream_tail\n\t stream_map_example\n\nconst my_stream = pair(4, () => pair(5, () => null));\n\nconst my_stream_2 =\n stream_map(display, my_stream);\n\nstream_ref(my_stream_2, 1);\nstream_ref(my_stream_2, 1);\n// the number 5 is shown twice\n// because the same delayed\n// object is forced twice\n\nconst my_stream = pair(4, () => pair(5, () => null));\nlet calls = 0;\nconst my_stream_2 =\n stream_map(x => {calls = calls + 1;}, my_stream);\n\nstream_ref(my_stream_2, 1);\nstream_ref(my_stream_2, 1);\ncalls;\n```\n\nwe can define an optimized function stream_map as follows:\n\n```javascript\nstream_map_optimized\n\t memo\n\t stream_map_optimized_example\n\t 2\n\nfunction stream_map_optimized(f, s) {\n return is_null(s)\n ? null\n : pair(f(head(s)),\n memo(() =>\n stream_map_optimized(f, stream_tail(s))));\n}\n```\n\n```javascript\nstream_map_optimized_example\n\nconst my_stream = pair(4, () => pair(5, () => null));\n\nconst my_stream_2 =\n stream_map(display, my_stream);\n\nstream_ref(my_stream_2, 1);\nstream_ref(my_stream_2, 1);\n// the number 5 is shown twice\n// because the same delayed\n// object is forced twice\n\nconst my_stream_3 =\n stream_map_optimized(display, my_stream);\n\nstream_ref(my_stream_3, 1);\nstream_ref(my_stream_3, 1);\n// the number 5 is shown only once\n// because the result of forcing\n// the delayed object is memoized\n\nconst my_stream = pair(4, () => pair(5, () => null));\n\nconst my_stream_2 =\n stream_map(x => x, my_stream);\n\nstream_ref(my_stream_2, 1);\nstream_ref(my_stream_2, 1);\n// the number 5 is shown twice\n// because the same delayed\n// object is forced twice\n\nlet calls = 0;\nconst my_stream_3 =\n stream_map_optimized(x => {calls = calls + 1;}, my_stream);\n\nstream_ref(my_stream_3, 1);\nstream_ref(my_stream_3, 1);\ncalls;\n```", + "token_count": 230, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Streams Are Delayed Lists", + "chunk_index": 8, + "chunk_id": "Modularity_Objects_and_State_Streams_Are_Delayed_Lists_8" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Metalinguistic Abstraction", - "subsection": "Logic Programming", - "chunk_id": "chapter4_chunks_Logic_Programming_1", + "content": "The function at the end of the preceding section shows how we can use streams to model signal-processing systems that contain is modeled by the\n\nfact that s internal stream integ is defined in terms of itself:\n\n```javascript\nconst integ = pair(initial_value,\n () => add_streams(scale_stream(integrand, dt),\n integ));\n```\n\n```javascript\nThe interpreters ability to deal with such an implicit definition\n\tdepends on the delay resulting from wrapping the call to\n\tadd_streams in a lambda expression.\n\tWithout this delay, the interpreter could not\n\tconstruct\n```\n\nUnfortunately, stream models of systems with loops may require uses of a\ndelay beyond the stream programming pattern seen so far.\n\nFor instance,\nfigure shows a\nsignal-processing system for solving the\n$dy/dt=f(y)$ where\n$f$ is a given function.\n\nThe figure shows a\nmapping component, which applies $f$ to its\ninput signal, linked in a feedback loop to an integrator in a manner\nvery similar to that of the analog computer circuits that are actually\nused to solve such equations.\n\nAn\nanalog computer circuit that solves the equation\n$dy/dt = f(y)$.\n\nAssuming we are given an initial value $y_0$ for $y$ , we could try to model this system using the function\n\n```javascript\nfunction solve(f, y0, dt) {\n const y = integral(dy, y0, dt);\n const dy = stream_map(f, y);\n return y;\n}\n```\n\nThis function does not work, because in the first line of\n\nOn the other hand, the intent of our definition does make sense, because we can, in principle, begin to generate the Indeed, For in the\n\nsecond line of\n\nTo take advantage of this idea, we will redefine\ndelayed argument.\n\nThe function\nthe integrand to be evaluated only when it is required to generate more than\nthe first element of the output stream:", + "token_count": 288, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Streams and Delayed Evaluation", "chunk_index": 1, - "content": "In chapter we stressed that computer science deals with\nMost programming languages, including Lisp, JavaScript, are organized around computing the values of mathematical functions. Expression-oriented languages (such as Lisp, Fortran, Algol and JavaScript) (such as Lisp, C, Python, and JavaScript) capitalize on the pun that an expression that describes the value of a function may also be interpreted as a means of computing that value. Because of this, most programming languages are strongly biased toward unidirectional computations (computations with well-defined inputs and outputs). There are, however, radically different programming languages that relax this bias. We saw one such example in section , where the objects of computation were arithmetic constraints. In a constraint system the direction and the order of computation are not so well specified; in carrying out a computation the system must therefore provide more detailed how to knowledge than would be the case with an ordinary arithmetic computation. This does not mean, however, that the user is released altogether from the responsibility of providing imperative knowledge. There are many constraint networks that implement the same set of constraints, and the user must choose from the set of mathematically equivalent networks a suitable network to specify a particular computation. The nondeterministic program evaluator of section also moves away from the view that programming is about constructing algorithms for computing unidirectional functions. In a nondeterministic language, expressions can have more than one value, and, as a result, the computation is dealing with unification .", - "token_count": 295, - "source_files": [ - "section4.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Streams_and_Delayed_Evaluation_1" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Metalinguistic Abstraction", - "subsection": "Logic Programming", - "chunk_id": "chapter4_chunks_Logic_Programming_2", + "content": "The function\nthe integrand to be evaluated only when it is required to generate more than\nthe first element of the output stream:\n\n```javascript\nintegral_example_4\n\nfunction numbers_starting_from(t, dt) {\n return pair(t,\n () => numbers_starting_from(t + dt, dt)\n );\n}\nconst dt = 0.01;\nconst linear = numbers_starting_from(0, dt);\nconst linear_integral = integral(() => linear, 0, dt);\n// computing integral from 0 to 3 of f(x) = x\n// (the integral is g(x) = 0.5 x^2, and therefore\n// the result is near 0.5 * 3^2 = 4.5)\nstream_ref(linear_integral, math_round(3 / dt));\n```\n\n```javascript\nintegral\n add_streams\n scale_stream\n integral_example_4\n 4.484999999999992\n\nfunction integral(delayed_integrand, initial_value, dt) {\n const integ =\n pair(initial_value,\n () => {\n const integrand = delayed_integrand();\n return add_streams(scale_stream(integrand, dt),\n integ);\n });\n return integ;\n}\n```\n\nNow we can implement our function by delaying the evaluation of declaration of\n\n```javascript\nsolve\n e\n\nfunction solve(f, y0, dt) {\n const y = integral(() => dy, y0, dt);\n const dy = stream_map(f, y);\n return y;\n}\n```\n\nIn general, every caller of\ndelay\nthe integrand argument.\n\nWe can demonstrate that the\nfunction\nworks by approximating\n$e\\approx 2.718$ by computing the value at\n$y=1$ of the solution to the differential\nequation $dy/dt=y$ with initial condition\n$y(0)=1$ :", + "token_count": 200, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Streams and Delayed Evaluation", "chunk_index": 2, - "content": "This approach, when it works, can be a very what is fact can be used to solve a number of different problems that would have different how to components. As an example, consider the append operation, which takes two lists as arguments and combines their elements to form a single list. In a procedural language such as Lisp, JavaScript, we could define append in terms of the basic list constructor cons , pair , as we did in section : (define (append x y) (if (null? x) y (cons (car x) (append (cdr x) y)))) function append(x, y) { return is_null(x) ? y : pair(head(x), append(tail(x), y)); } This procedure function can be regarded as a translation into Lisp JavaScript of the following two rules, the first of which covers the case where the first list is empty and the second of which handles the case of a nonempty list, which is a cons pair of two parts: For any list y , the empty list and y append to form y . For any u , v , y , and z , (cons u v) pair(u, v) and y append to form (cons u z) pair(u, z) if v and y append to form z . Using the append procedure, function, we can answer questions such as Find the append of (a b) list(\"a\", \"b\") and (c d) . list(\"c\", \"d\") .", - "token_count": 300, - "source_files": [ - "section4.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Streams_and_Delayed_Evaluation_2" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Metalinguistic Abstraction", - "subsection": "Logic Programming", - "chunk_id": "chapter4_chunks_Logic_Programming_3", + "content": "We can demonstrate that the\nfunction\nworks by approximating\n$e\\approx 2.718$ by computing the value at\n$y=1$ of the solution to the differential\nequation $dy/dt=y$ with initial condition\n$y(0)=1$ :\n\n```javascript\nsolve_optimized\n\nfunction memo(fun) {\n let already_run = false;\n let result = undefined;\n return () => {\n if (!already_run) {\n result = fun();\n already_run = true;\n return result;\n } else {\n return result;\n }\n\t };\n}\n// note the use of the memoization optimization\nfunction stream_combine(f, s1, s2) {\n return is_null(s1) && is_null(s2)\n ? null\n : is_null(s1) || is_null(s2)\n ? error(null, \"unexpected argument -- stream_combine\")\n : pair(f(head(s1), head(s2)),\n memo(() => stream_combine(f, stream_tail(s1),\n stream_tail(s2))));\n}\nfunction add_streams(s1, s2) {\n return stream_combine((x1, x2) => x1 + x2, s1, s2);\n}\nfunction scale_stream(stream, factor) {\n return stream_map(x => x * factor,\n stream);\n}\n// note the use of the memoization optimization\nfunction integral(delayed_integrand, initial_value, dt) {\n const integ =\n pair(initial_value,\n memo(() => {\n const integrand = delayed_integrand();\n return add_streams(scale_stream(integrand, dt),\n integ);\n }));\n return integ;\n}\nfunction solve(f, y0, dt) {\n const y = integral(() => dy, y0, dt);\n const dy = stream_map(f, y);\n return y;\n}\n```\n\n```javascript\ne\n solve_optimized\n 2.716923932235896\n\nstream_ref(solve(y => y, 1, 0.001), 1000);\n```\n\nWrite a\nfunction\n$R$ , $L$ , and\n$C$ of the circuit and the time increment\n$dt$.\n\nIn a manner similar to that of the\nfunction\nof exercise ,\nfunction\nthat takes the initial values of the state variables,\n$v_{C_{0}}$ and\n$i_{L_{0}}$ , and produces a pair\n(using pair)\nof the streams of states $v_{C}$ and\n$i_{L}$.\n\nUsing $R = 1$ ohm,\n$C= 0.2$ farad,\n$L = 1$ henry,\n$dt = 0.1$ second, and initial values\n$i_{L_{0}} = 0$ amps and\n$v_{C_{0}} = 10$ volts.", + "token_count": 280, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Streams and Delayed Evaluation", "chunk_index": 3, - "content": "But the same two rules are also sufficient for answering the following sorts of questions, which the procedure function can t answer: Find a list y that append s with (a b) list(\"a\", \"b\") to produce (a b c d) . list(\"a\", \"b\", \"c\", \"d\") . Find all x and y that append to form (a b c d) . list(\"a\", \"b\", \"c\", \"d\") . In a append procedure function by stating the two rules about append given above. How to knowledge is provided automatically by the interpreter to allow this single pair of rules to be used to answer all three types of questions about append . Contemporary logic programming languages (including the one we implement here) have substantial deficiencies, in that their general how to methods can lead them into spurious infinite loops or other undesirable behavior. Logic programming is an active field of research in computer science. Earlier in this chapter we explored the technology of implementing interpreters and described the elements that are essential to an interpreter for a Lisp-like JavaScript-like language (indeed, to an interpreter for any conventional language). Now we will apply these ideas to discuss an interpreter for a logic programming language. We call this language the query language , because it is very useful for retrieving information from data bases by formulating queries , or questions, expressed in the language.", - "token_count": 277, - "source_files": [ - "section4.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Streams_and_Delayed_Evaluation_3" }, { - "chapter_file": "chapter4_chunks.json", - "section": "Metalinguistic Abstraction", - "subsection": "Logic Programming", - "chunk_id": "chapter4_chunks_Logic_Programming_4", + "content": "Using $R = 1$ ohm,\n$C= 0.2$ farad,\n$L = 1$ henry,\n$dt = 0.1$ second, and initial values\n$i_{L_{0}} = 0$ amps and\n$v_{C_{0}} = 10$ volts.\n\nThe examples in this section illustrate how\ndelayed evaluation\nprovides great programming flexibility, but the same examples also show how\nthis can make our programs more complex.\n\nOur new\nfunction,\nfor instance, gives us the power to model systems with loops, but we must\nnow remember that\nfunction\nthat uses\nfunctions:\nordinary\nfunctions\nand\nfunctions\nthat take delayed arguments.\n\nIn general, creating separate classes of\nfunctions\nforces us to create separate classes of higher-order\nfunctions\nas well.\n\nOne way to avoid the need for two different classes of\nfunctions\nis to make all\nfunctions\ntake delayed arguments.\n\nWe could adopt a model of evaluation in which all\narguments to\nfunctions\nare automatically delayed and arguments are forced only when they are\nactually needed (for example, when they are required by a primitive\noperation).\n\nThis would transform our language to use normal-order\nevaluation, which we first described when we introduced the substitution\nmodel for evaluation in section.\n\nConverting to normal-order evaluation provides a uniform and elegant way to\nsimplify the use of delayed evaluation, and this would be a natural strategy\nto adopt if we were concerned only with stream processing.\n\nIn\nsection , after we have studied the\nevaluator, we will see how to transform our language in just this way.\n\nUnfortunately, including delays in\nfunction\ncalls wreaks havoc with our ability to design programs that depend on the\norder of events, such as programs that use assignment, mutate data, or\nperform input or output.\n\n```javascript\nEven a single delay in the tail of a pair can cause great confusion, as\n\tillustrated by exercises\n\tand.\n```", + "token_count": 294, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Streams and Delayed Evaluation", "chunk_index": 4, - "content": "Even though the query language is very different from Lisp, JavaScript, we will find it convenient to describe the language in terms of the same general framework we have been using all along: as a collection of primitive elements, together with means of combination that enable us to combine simple elements to create more complex elements and means of abstraction that enable us to regard complex elements as single conceptual units. An interpreter for a logic programming language is considerably more complex than an interpreter for a language like Lisp. JavaScript. Nevertheless, we will see that our . In particular, there will be an evaluate part that classifies expressions according to type and an apply part that implements the language s abstraction mechanism (procedures (functions in the case of Lisp, JavaScript, and rules in the case of logic programming). Also, a central role is played in the implementation by a frame data structure, which determines the correspondence between symbols and their associated values. One additional interesting aspect of our query-language implementation is that we make substantial use of streams, which were introduced in chapter .", - "token_count": 206, - "source_files": [ - "section4.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Streams_and_Delayed_Evaluation_4" }, { - "chapter_file": "chapter5_chunks.json", - "section": null, - "subsection": "Computing with Register Machines", - "chunk_id": "chapter5_chunks_Computing_with_Register_Machines_1", - "chunk_index": 1, - "content": "We began this book by studying processes and by describing processes in terms of procedures functions written in Lisp. JavaScript. To explain the meanings of these procedures, functions, we used a succession of models of evaluation: the substitution model of chapter , the environment model of chapter , and the metacircular evaluator of chapter . Our examination of the metacircular evaluator, in particular, dispelled much of the mystery of how Lisp-like languages are interpreted. JavaScript-like languages are interpreted. But even the metacircular evaluator leaves important questions unanswered, because it fails to elucidate the mechanisms of control in a Lisp JavaScript system. For instance, the evaluator does not explain how the evaluation of a subexpression manages to return a value to the expression that uses this value , nor does the evaluator explain how some recursive procedures generate iterative processes (that is, are evaluated using constant space) whereas other recursive procedures generate recursive processes . These questions remain unanswered because the metacircular evaluator is itself a Lisp program and hence inherits the control structure of the underlying Lisp system. In order to provide a more complete description of the control structure of the Lisp evaluator, we must work at a more primitive level than Lisp itself. Also, the evaluator does not explain how some recursive functions can generate iterative processes (that is, be evaluated using constant space) whereas other recursive functions will generate recursive processes. In this chapter we We will describe processes in terms of the step-by-step operation of a traditional computer.", - "token_count": 291, - "source_files": [ - "chapter5.xml" - ] + "content": "Unfortunately, including delays in\nfunction\ncalls wreaks havoc with our ability to design programs that depend on the\norder of events, such as programs that use assignment, mutate data, or\nperform input or output.\n\nAs far as anyone knows, mutability and delayed evaluation do not mix well\nin programming\nlanguages.\n\nA signal-flow diagram for the solution to a series RLC circuit.", + "token_count": 61, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Streams and Delayed Evaluation", + "chunk_index": 5, + "chunk_id": "Modularity_Objects_and_State_Streams_and_Delayed_Evaluation_5" }, { - "chapter_file": "chapter5_chunks.json", - "section": null, - "subsection": "Computing with Register Machines", - "chunk_id": "chapter5_chunks_Computing_with_Register_Machines_2", - "chunk_index": 2, - "content": "Such a computer, or register machine , sequentially executes instructions that manipulate the contents of a fixed set of storage elements called registers . A typical register-machine instruction applies a primitive operation to the contents of some registers and assigns the result to another register. Our descriptions of processes executed by register machines will look very much like machine-language programs for traditional computers. However, instead of focusing on the machine language of any particular computer, we will examine several Lisp JavaScript procedures functions and design a specific register machine to execute each procedure. function. Thus, we will approach our task from the perspective of a hardware architect rather than that of a machine-language computer programmer. In designing register machines, we will develop mechanisms for implementing important programming constructs such as recursion. We will also present a language for describing designs for register machines. In section we will implement a Lisp JavaScript program that uses these descriptions to simulate the machines we design. Most of the primitive operations of our register machines are very simple. For example, an operation might add the numbers fetched from two registers, producing a result to be stored into a third register. Such an operation can be performed by easily described hardware. In order to deal with list structure, however, we will also use the memory operations car , head , cdr , tail , and cons , pair , which require an elaborate storage-allocation mechanism. In section we study their implementation in terms of more elementary operations.", - "token_count": 279, - "source_files": [ - "chapter5.xml" - ] + "content": "When we introduced compound functions in chapter , we used the ) to define what is meant by applying a function to arguments: - -\n\nTo apply a compound function to arguments, evaluate the return expression of the function (more generally, the body) with each parameter replaced by the corresponding\n\nargument.\n\nOnce we admit assignment into our programming language, such a\ndefinition is no longer adequate.\n\nIn particular,\nsection argued that, in the\npresence of assignment,\n\n```javascript\na name cannot be considered to be merely\n\trepresenting a value. Rather, a name must somehow designate a\n\tplace in which values can be stored.\n```\n\nIn our new model of evaluation, these places will be maintained in structures called environments.\n\nAn environment is a sequence of\nframes.\n\nEach frame is a table (possibly empty) of\nbindings , which associate\nnames\nwith their corresponding\nvalues.\n\n(A single frame may contain at most one binding for any name.)\nEach frame also has a pointer to its\nenclosing environment , unless, for the purposes of discussion, the\nframe is considered to be\nglobal.\n\nThe\nvalue of a name\nwith respect to an environment is the value given by the binding of\nthe\nname\nin the first frame in the environment that contains a\nbinding for that\nname.\n\nIf no frame in the sequence specifies a\nbinding for the\nname,\nthen the\nname\nis said to be\nunbound in the environment.\n\nA simple\n\nFigure\nshows a simple environment\nstructure consisting of three frames, labeled I, II, and III.\n\nIn the\ndiagram, A, B, C, and D are pointers to environments.\n\nC and D point\nto the same environment.\n\nThe\nnames\nII, while\nshadow the binding of\n\nThe environment is crucial to the evaluation process, because it determines\nthe context in which an expression should be evaluated.", + "token_count": 300, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_The_Environment_Model_of_Evaluation_1" }, { - "chapter_file": "chapter5_chunks.json", - "section": null, - "subsection": "Computing with Register Machines", - "chunk_id": "chapter5_chunks_Computing_with_Register_Machines_3", - "chunk_index": 3, - "content": "In section , after we have accumulated experience formulating simple procedures functions as register machines, we will design a machine that carries out the algorithm described by the metacircular evaluator of section . This will fill in the gap in our understanding of how Scheme expressions JavaScript programs are interpreted, by providing an explicit model for the mechanisms of control in the evaluator. In section we will study a simple compiler that translates Scheme JavaScript programs into sequences of instructions that can be executed directly with the registers and operations of the evaluator register machine.", - "token_count": 102, - "source_files": [ - "chapter5.xml" - ] + "content": "The environment is crucial to the evaluation process, because it determines\nthe context in which an expression should be evaluated.\n\nIndeed, one could\nsay that expressions in a programming language do not, in themselves, have\nany meaning.\n\nRather, an expression acquires a meaning only with respect to\nsome environment in which it is evaluated.\n\n```javascript\nEven the interpretation of an expression as straightforward as\n\tdisplay(1) depends on an\n\tunderstanding that one is operating in a context in which the name\n```\n\nThus, in our model of evaluation we will always speak of evaluating an\nexpression with respect to some environment.\n\nTo describe interactions with\nthe interpreter, we will suppose that there is a\nnames\nassociated with the\nprimitive\nfunctions.\n\nFor example, the idea that\n\n```javascript\ndisplay is the name for the\n\tprimitive display function is captured by saying that the\n\tname display\n```\n\nis bound in the global environment to the primitive display function.\n\nBefore we evaluate a program, we extend the global\nenvironment with a new frame, the\nprogram frame , resulting in the\nprogram environment.\n\nWe will add\nthe names that are declared at the top level of the\nprogram, outside of any block, to this frame.\n\nThe given program\nis then evaluated with respect to the program environment.", + "token_count": 211, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_The_Environment_Model_of_Evaluation_2" }, { - "chapter_file": "chapter5_chunks.json", - "section": "Compilation", - "subsection": "An Example of Compiled Code", - "chunk_id": "chapter5_chunks_An_Example_of_Compiled_Code_1", + "content": "we showed how the\n\n```javascript\napplication\n f(5)\n```\n\nevaluates to 136, given the following function declarations:\n\n```javascript\nf_example\n 136\n\nfunction square(x) {\n return x * x;\n}\nfunction sum_of_squares(x, y) {\n return square(x) + square(y);\n}\nfunction f(a) {\n return sum_of_squares(a + 1, a * 2);\n}\n```\n\nWe can analyze the same example using the environment model.\n\nFigure shows the three\nfunction\nobjects created by evaluating the definitions of\nsum_of_squares\nin the\nprogram\nenvironment.\n\nEach\nfunction\nobject consists of some code, together with a pointer to the\nprogram\nenvironment.\n\nFunction objects in the program frame.\n\nfigure\nwe see the environment structure created by evaluating the expression\nf(5).\n\nThe call to parameter of\n\n```javascript\nreturn sum_of_squares(a + 1, a * 2);\n```\n\nTo evaluate\n\n```javascript\nthe return statement, we first evaluate the\n\tsubexpressions of the return expression.\n```\n\nThe first subexpression,\nsum_of_squares,\nhas a value that is a\nfunction\nobject.\n\n(Notice how this value is found: We first look in the first frame\nof E1, which contains no binding for\nsum_of_squares.\n\nThen we proceed to the enclosing environment, i.e., the\nprogram\nenvironment, and find the binding shown in\nfigure.)\nThe other two subexpressions are evaluated by applying the primitive\noperations\na + 1\nand\na * 2\nto obtain 6 and 10, respectively.\n\nNow we apply the\nfunction\nobject\nsum_of_squares\nto the arguments 6 and 10.\n\nThis results in a new environment, E2, in which\nthe parameters\n\n```javascript\nthe statement\n\nreturn square(x) + square(y);\n```\n\nThis leads us to evaluate square(x), where program frame and\n\n```javascript\nreturn x * x;.\n```\n\nAlso as part of applying sum_of_squares, we must evaluate the subexpression square(y), where parameter of\n\n```javascript\nreturn x * x;.\n```\n\nThe important point to observe is that each call to program environment, since this is the environment indicated by the function object.\n\nAfter the subexpressions are evaluated, the results are returned.", + "token_count": 313, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": "Applying Simple Functions", "chunk_index": 1, - "content": "Now that we have seen all the elements of the compiler, let us examine an example of compiled code to see how things fit together. We will compile the definition declaration of a recursive factorial factorial procedure function by calling compile : by passing as first argument to compile the result of applying parse to a string representation of the program (here using ` $\\ldots$ ` , which work like single and double quotation marks but allow the string to span multiple lines): (compile '(define (factorial n) (if (= n 1) 1 (* (factorial (- n 1)) n))) 'val 'next) compile(parse(` function factorial(n) { return n === 1 ? 1 : factorial(n - 1) * n; } `), \"val\", \"next\"); We have specified that the value of the define expression declaration should be placed in the val register. We don t care what the compiled code does after executing the define , declaration, so our choice of next \"next\" as the linkage descriptor is arbitrary. Compile determines that the expression is a definition, so it The function compile determines that it was given a function declaration, so it transforms it to a constant declaration and then calls compile-definition to compile compile_declaration .", - "token_count": 250, - "source_files": [ - "subsection5.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Applying_Simple_Functions_1" }, { - "chapter_file": "chapter5_chunks.json", - "section": "Compilation", - "subsection": "An Example of Compiled Code", - "chunk_id": "chapter5_chunks_An_Example_of_Compiled_Code_2", + "content": "After the subexpressions are evaluated, the results are returned.\n\nThe\nvalues generated by the two calls to\nsum_of_squares,\nand this result is returned by.", + "token_count": 24, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": "Applying Simple Functions", "chunk_index": 2, - "content": "This compiles code to compute the value to be assigned (targeted to val ), followed by code to install the definition, declaration, followed by code to put the value of the define (which is the symbol ok ) declaration (which is the value undefined ) into the target register, followed finally by the linkage code. Env The env register is preserved around the computation of the value, because it is needed in order to install the definition. declaration. Because the linkage is next , \"next\" , there is no linkage code in this case. The skeleton of the compiled code is thus $\\langle save$ env $if\\ modified\\ by\\ code\\ to\\ compute\\ value\\rangle$ $\\langle compilation\\ of\\ definition\\ value, target$ val$, linkage$ next$\\rangle$ $\\langle restore$ env $if\\ saved\\ above\\rangle$ (perform (op define-variable!) (const factorial) (reg val) (reg env)) (assign val (const ok)) save env if modified by code to compute value compilation of declaration value, target val , linkage \"next\" restore env if saved above perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))\nThe expression that is to be compiled to produce the value for the variable name factorial is a lambda lambda expression whose value is the procedure function that computes factorials.", - "token_count": 286, - "source_files": [ - "subsection5.xml" - ] + "chunk_id": "Modularity_Objects_and_State_Applying_Simple_Functions_2" }, { - "chapter_file": "chapter5_chunks.json", - "section": "Compilation", - "subsection": "An Example of Compiled Code", - "chunk_id": "chapter5_chunks_An_Example_of_Compiled_Code_3", - "chunk_index": 3, - "content": "Compile The function compile handles this by calling compile-lambda , compile_lambda_expression , which compiles the procedure function body, labels it as a new entry point, and generates the instruction that will combine the procedure function body at the new entry point with the runtime environment and assign the result to val . The sequence then skips around the compiled procedure function code, which is inserted at this point. The procedure function code itself begins by extending the procedure s definition function s declaration environment by a frame that binds the formal parameter n to the procedure function argument. Then comes the actual procedure function body. Since this code for the value of the variable name doesn t modify the env register, the optional save and restore shown above aren t generated. (The procedure function code at entry2 entry1 isn t executed at this point, so its use of env is irrelevant.) Therefore, the skeleton for the compiled code becomes (assign val (op make-compiled-procedure) (label entry2) (reg env)) (goto (label after-lambda1)) entry2 (assign env (op compiled-procedure-env) (reg proc)) (assign env (op extend-environment) (const (n)) (reg argl) (reg env)) $\\langle compilation\\ of\\ procedure\\ body\\rangle$ after-lambda1 (perform (op define-variable!)", - "token_count": 269, - "source_files": [ - "subsection5.xml" - ] + "content": "The environment model as presented so far focuses on how functions can refer\nto their parameters, locally declared names, and names that are declared\noutside the function.\n\nWe achieve this by evaluating statements and expressions\nwith respect to a current environment.\n\nIt does not specify how\nwe keep track of environments as computation proceeds.\n\nFor example, when we\nevaluate an expression f(x) + y , we\nneed to evaluate x in the current\nenvironment, establish as the new current environment the environment of\nf extended by a binding of its\nparameter to the value of x , and\nevaluate the body of f in this\nextended environment.\n\nBut what environment should we use for evaluating\ny after\nf returns?\n\nIn this section, we extend the\n\nExercise shows that the presence of\nassignments makes the result of a program depend on the order in which\nthe operands of an operator combination are evaluated.\n\nTo remove\nambiguities that arise from this, the JavaScript standard specifies\nleft-to-right evaluation of operands.\n\nAs an example, consider the evaluation of the arithmetic statement\n\n```javascript\n1 + (2 * 3);\n```\n\nThe expression is decomposed into its operands 1 and 2 * 3 , followed by the instruction to add their results.", + "token_count": 205, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": "CSE Machine", + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_CSE_Machine_1" }, { - "chapter_file": "chapter5_chunks.json", - "section": "Compilation", - "subsection": "An Example of Compiled Code", - "chunk_id": "chapter5_chunks_An_Example_of_Compiled_Code_4", - "chunk_index": 4, - "content": "(const factorial) (reg val) (reg env)) (assign val (const ok)) $\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"), label(\"entry1\"), reg(\"env\"))), go_to(label(\"after_lambda2\")), \"entry1\", assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))), assign(\"env\", list(op(\"extend_environment\"), constant(list(\"n\")), reg(\"argl\"), reg(\"env\"))), compilation of function body \"after_lambda2\", perform(list(op(\"assign_symbol_value\"), constant(\"factorial\"), reg(\"val\"), reg(\"env\"))), assign(\"val\", constant(undefined))\nA procedure function body is always compiled (by compile-lambda-body ) compile_lambda_body ) as a sequence with target val and linkage return . \"next\" . The sequence body in this case consists of a single if expression: return statement: (if (= n 1) 1 (* (factorial (- n 1)) n)) return n === 1 ? 1 : factorial(n - 1) * n; The function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target val and linkage \"return\" , because its value is to be returned from the function.", - "token_count": 275, - "source_files": [ - "subsection5.xml" - ] + "content": "We can turn to the environment model to see how\nfunctions\nand assignment can be used to represent objects with local state.\n\nAs an\nexample, consider the\nwithdrawal processor from\nsection created by calling the\nfunction\n\n```javascript\nmake_withdraw2\n\nfunction make_withdraw(balance) {\n return amount => {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"insufficient funds\";\n }\n };\n}\n```\n\nLet us describe the evaluation of\n\n```javascript\nmake_withdraw2\n make_withdraw2_w1_declare\n\nconst W1 = make_withdraw(100);\n```\n\nfollowed by\n\n```javascript\nmake_withdraw2_w1_example\n make_withdraw2_w1_declare\n 50\n\nW1(50);\n```\n\nFigure shows the result of\n\n```javascript\ndeclaring the\n make_withdraw\n```\n\nfunction\nin the\nprogram\nenvironment.\n\nThis produces a\nfunction\nobject that contains a pointer to the\nprogram\nenvironment.\n\nSo far, this is no different from the examples we have already\nseen, except that\n\n```javascript\nthe return expression in the body of the function is itself\n\ta lambda expression.\n```\n\n```javascript\nResult of defining\n\t make_withdraw in\n\t the program environment.\n```\n\nThe interesting part of the computation happens when we apply the function make_withdraw to an argument:\n\n```javascript\nconst W1 = make_withdraw(100);\n```\n\nWe begin, as usual, by setting up an environment E1 in which the parameter make_withdraw, namely the\n\n```javascript\nreturn statement whose return expression is\n a lambda expression. The evaluation of this lambda expression\n```\n\nconstructs a new\nfunction\nobject, whose code is as specified by the\nlambda expression\nand whose environment is E1, the environment in which the\nlambda expression\nwas evaluated to produce the\nfunction.\n\nThe resulting\nfunction\nobject is the value returned by the call to\nmake_withdraw.\n\nThis is bound to\nprogram\nenvironment, since the\nconstant declaration\nitself is being evaluated in the\nprogram\nenvironment.\n\nFigure\nshows the resulting environment structure.\n\n```javascript\nResult of evaluating\n\t const W1 = make_withdraw(100);.\n```\n\nNow we can analyze what happens when\n\n```javascript\nW1(50);\n```", + "token_count": 302, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": "Frames as the Repository of Local State", + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_Frames_as_the_Repository_of_Local_State_1" }, { - "chapter_file": "chapter5_chunks.json", - "section": "Compilation", - "subsection": "An Example of Compiled Code", - "chunk_id": "chapter5_chunks_An_Example_of_Compiled_Code_5", - "chunk_index": 5, - "content": "Compile-if The return expression is a conditional expression, for which compile_conditional generates code that first computes the predicate (targeted to val ), then checks the result and branches around the true branch if the predicate is false. Env Registers env and continue are preserved around the predicate code, since they may be needed for the rest of the if conditional expression. Since the if expression is the final expression (and only expression) in the sequence making up the procedure body, its target is val and its linkage is return , so the The true and false branches are both compiled with target val and linkage return . \"return\" . (That is, the value of the conditional, which is the value computed by either of its branches, is the value of the procedure.) function.) $\\langle save$ continue, env $if\\ modified\\ by\\ predicate\\ and\\ needed\\ by\\ branches\\rangle$ $\\langle compilation\\ of\\ predicate, target$ val$,\\ linkage$ next$\\rangle$ $\\langle restore$ continue, env $if\\ saved\\ above\\rangle$ (test (op false?)", - "token_count": 220, - "source_files": [ - "subsection5.xml" - ] + "content": "Now we can analyze what happens when\n\nWe begin by constructing a frame in which\nparameter of\nprogram\nenvironment, but rather the environment E1, because this is the\nenvironment that is specified by the\nfunction\nobject.\n\nWithin this new environment, we evaluate the body of the\nfunction:\n\n```javascript\nif (balance >= amount) {\n balance = balance - amount;\n return balance;\n} else {\n return \"insufficient funds\";\n}\n```\n\nThe resulting environment structure is shown in\nfigure.\n\nThe expression being evaluated references\nboth\nThe variable amount\nwill be found in the first frame in the environment, and\n\n```javascript\nEnvironments created by applying the function object\n\t W1.\n```\n\nWhen the\nassignment\nis executed, the binding of\nfunction\nobject\nfunction\ncall that constructed it has terminated, and there are no pointers to that\nframe from other parts of the environment.\n\nThe next time\nplace that holds the local\nstate variable for the\nfunction\nobject\nFigure\nshows the situation after the call to\n\n```javascript\nEnvironments after the call to\n\t W1.\n```\n\nObserve what happens when we create a second withdraw object by making another call to make_withdraw:\n\n```javascript\nmake_withdraw2\n make_withdraw2_w2_declare\n 20\n\nconst W2 = make_withdraw(100);\n\nconst W1 = make_withdraw(100);\nW1(50);\nconst W2 = make_withdraw(100);\nW2(80);\n```\n\nThis produces the environment structure of\nfigure,\nwhich shows\nthat\nfunction\nobject, that is, a pair with some code and an environment.\n\nThe environment\nE2 for\nmake_withdraw.\n\nIt contains a frame with its own local binding for\nlambda\nexpression in the body of\nmake_withdraw.\n\n```javascript\nUsing\n\t const W2 = make_withdraw(100);\n\t to create a second object.\n```", + "token_count": 258, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": "Frames as the Repository of Local State", + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_Frames_as_the_Repository_of_Local_State_2" }, { - "chapter_file": "chapter5_chunks.json", - "section": "Compilation", - "subsection": "An Example of Compiled Code", - "chunk_id": "chapter5_chunks_An_Example_of_Compiled_Code_6", - "chunk_index": 6, - "content": "(reg val)) (branch (label false-branch4)) true-branch5 $\\langle compilation\\ of\\ true\\ branch, target$ val$,\\ linkage$ return$\\rangle$ false-branch4 $\\langle compilation\\ of\\ false\\ branch, target$ val$,\\ linkage$ return$\\rangle$ after-if3 $\\texttt{ }\\texttt{ }$revert_stack_to_marker(), restore(\"continue\"), save continue , env if modified by predicate and needed by branches compilation of predicate, target val , linkage \"next\" restore continue , env if saved above test(list(op(\"is_falsy\"), reg(\"val\"))), branch(label(\"false_branch4\")), \"true_branch3\", compilation of true branch, target val , linkage \"return\" \"false_branch4\", compilation of false branch, target val , linkage \"return\" \"after_cond5\",", - "token_count": 174, - "source_files": [ - "subsection5.xml" - ] + "content": "In this section we handle the evaluation of function bodies or other\nblocks (such as the branches of conditional statements) that contain\ndeclarations.\n\nEach block opens a new scope for names declared in the block.\n\nIn order to evaluate a block in a given environment, we extend that\nenvironment by a new frame that contains all names declared directly\n(that is, outside of nested blocks) in the body of the block and\nthen evaluate the body in the newly constructed environment.\n\nSection introduced the idea that functions can have internal declarations, thus leading to a block structure as in the function to compute square roots:\n\n```javascript\nanother_sqrt\n abs_definition\n square_definition\n average_definition\n sqrt_example7\n 2.2360688956433634\n\nfunction sqrt(x) {\n function is_good_enough(guess) {\n return abs(square(guess) - x) < 0.001;\n }\n function improve(guess) {\n return average(guess, x / guess);\n }\n function sqrt_iter(guess){\n return is_good_enough(guess)\n ? guess\n : sqrt_iter(improve(guess));\n }\n return sqrt_iter(1);\n}\n```\n\nNow we can use the environment model to see why these internal\ndeclarations\nbehave as desired.\n\nFigure\nshows the point in the evaluation of the expression\nsqrt(2)\nwhere the internal\nfunction\nis_good_enough\nhas been called for the first time with\n1.\n\nThe\n\nObserve the structure of the environment.\n\nThe name\nto a\nfunction\nobject whose associated environment is the\nprogram\nenvironment.\n\nWhen\nprogram\nenvironment, in which the parameter 2.\n\nThe body of E1.\n\n```javascript\nThat body is a block with local\n\tfunction declarations and therefore E1 was extended with a new frame for\n\tthose declarations, resulting in the new environment E2. The body\n\tof the block was then evaluated in E2. Since the first statement\n\tin the body is\n```\n\n```javascript\nabs_definition\n square_definition\n\nfunction is_good_enough(guess) {\n return abs(square(guess) - x) < 0.001;\n}\n```\n\n```javascript\nevaluating this declaration created the function\n\tis_good_enough\n\tin the environmentE2.\n```", + "token_count": 293, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": "Internal Declarations", + "chunk_index": 1, + "chunk_id": "Modularity_Objects_and_State_Internal_Declarations_1" }, { - "chapter_file": "chapter5_chunks.json", - "section": "Compilation", - "subsection": "An Example of Compiled Code", - "chunk_id": "chapter5_chunks_An_Example_of_Compiled_Code_7", - "chunk_index": 7, - "content": "The predicate (= n 1) n === 1 is a procedure call. function application (after transformation of the operator combination). This looks up the operator (the symbol = ) function expression (the symbol \"===\" ) and places this value in proc . fun . It then assembles the arguments 1 and the value of n into argl . Then it tests whether proc fun contains a primitive or a compound procedure, function, and dispatches to a primitive branch or a compound branch accordingly. Both branches resume at the after-call after_call label. The compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function. The requirements to preserve registers around the evaluation of the operator and operands function and argument expressions don t result in any saving of registers, because in this case those evaluations don t modify the registers in question. (assign proc (op lookup-variable-value) (const =) (reg env)) (assign val (const 1)) (assign argl (op list) (reg val)) (assign val (op lookup-variable-value) (const n) (reg env)) (assign argl (op cons) (reg val) (reg argl)) (test (op primitive-procedure?) (reg proc)) (branch (label primitive-branch17)) compiled-branch16 (assign continue (label after-call15)) (assign val (op compiled-procedure-entry) (reg proc)) (goto (reg val)) primitive-branch17 (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) after-call15 $\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"===\"), reg(\"env\"))), assign(\"val\", constant(1)), assign(\"argl\", list(op(\"list\"), reg(\"val\"))), assign(\"val\", list(op(\"lookup_symbol_value\"), constant(\"n\"), reg(\"env\"))), assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))), test(list(op(\"is_primitive_function\"), reg(\"fun\"))), branch(label(\"primitive_branch6\")), \"compiled_branch7\", assign(\"continue\", label(\"after_call8\")), save(\"continue\"), push_marker_to_stack(), assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))), go_to(reg(\"val\")), \"primitive_branch6\", assign(\"val\", list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))), \"after_call8\",", - "token_count": 526, - "source_files": [ - "subsection5.xml" - ] + "content": "The body of E1.\n\n```javascript\nTobe more precise,\n\tthe name is_good_enough\n\tin the first frame of E2 was bound to a function\n\tobject whose associated environment is E2.\n```\n\nSimilarly,\nsqrt_iter\nwere defined as\nfunctions in E2.\n\nFor conciseness,\nfigure\nshows only the\nfunction\nobject for\nis_good_enough.\n\nAfter the local\nfunctions\nwere defined, the expression\nsqrt_@iter(1)\nwas evaluated, still in environment\nE2.\n\nSo the\nfunction\nobject bound to\n\n```javascript\nsqrt_@iter\n in E2 was called with 1 as an argument. This created an environment E3\n in which\n```\n\nsqrt_@iter,\nis bound to 1.\n\nThe function sqrt_@iter\nin turn called\nis_@good_@enough\nwith the value of\n\n```javascript\n(from E3) as the argument for\n\tis_@good_@enough.\n```\n\nThis set up another environment,\n\n```javascript\nE4, in which\n\tis_@good_@enough)\n```\n\nis bound to 1.\n\nAlthough\nsqrt_@iter\nand\nis_@good_@enough\nboth have a parameter named\n\n```javascript\nAlso, E3 and E4 both have E2 as their enclosing environment, because the\n\tsqrt_@iter\n\tand\n\tis_@good_@enough functions\n\tboth have E2 as their environment part.\n```\n\nOne consequence of this is that the name is_@good_@enough will reference the binding of function was called.\n\nThe environment model thus explains the two key properties that make local\nfunction declarations\na useful technique for modularizing programs:\n-\n-\nThe names of the local\nfunctions\ndo not interfere with\nnames external to the enclosing\nfunction,\nbecause the local\nfunction\nnames will be bound in the frame that the\nblock creates when it is evaluated,\nrather than being bound in the\nprogram\nenvironment.\n-\n-\nThe local\nfunctions\ncan access the arguments of the enclosing\nfunction,\nsimply by using parameter names as free\nnames.\n\nThis is\nbecause the body of the local\nfunction\nis evaluated in an environment that is subordinate to the\nevaluation environment for the enclosing\nfunction.\n\nAs we saw, the scope of the names declared in\nsqrt is the whole body of\nsqrt.", + "token_count": 307, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": "Internal Declarations", + "chunk_index": 2, + "chunk_id": "Modularity_Objects_and_State_Internal_Declarations_2" }, { - "chapter_file": "chapter5_chunks.json", - "section": "Compilation", - "subsection": "An Example of Compiled Code", - "chunk_id": "chapter5_chunks_An_Example_of_Compiled_Code_8", - "chunk_index": 8, - "content": "The true branch, which is the constant 1, compiles (with target val and linkage return ) \"return\" ) to (assign val (const 1)) (goto (reg continue)) $\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)), go_to(reg(\"continue\")), The code for the false branch is another procedure function call, where the procedure function is the value of the symbol * , \"*\" , and the arguments are n and the result of another procedure function call (a call to factorial ). Each of these calls sets up proc fun and argl and its own primitive and compound branches. Figure shows the complete compilation of the definition declaration of the factorial procedure. function. Notice that the possible save and restore of continue and env around the predicate, shown above, are in fact generated, because these registers are modified by the procedure function call in the predicate and needed for the procedure function call and the return \"return\" linkage in the branches.", - "token_count": 198, - "source_files": [ - "subsection5.xml" - ] + "content": "As we saw, the scope of the names declared in\nsqrt is the whole body of\nsqrt.\n\nThis explains why\nmutual recursion works, as in this (quite\nwasteful) way of checking whether a nonnegative\ninteger is even.\n\n```javascript\nf_is_even_is_odd\n\nfunction f(x) {\n function is_even(n) {\n return n === 0\n ? true\n : is_odd(n - 1);\n }\n function is_odd(n) {\n return n === 0\n ? false\n : is_even(n - 1);\n }\n return is_even(x);\n}\n```\n\nAt the time when\nis_even is called during a call to\nf , the environment diagram looks\nlike the one in figure when\nsqrt_iter is called.\n\nThe functions\nis_even and\nis_odd are bound in E2 to function objects\nthat point to E2 as the environment in which to evaluate calls to those\nfunctions.\n\nThus\nis_odd in the body of\nis_even refers to the right function.\n\nAlthough\nis_odd\nis defined after\nis_even ,\nthis is no different from how in the body of\nsqrt_iter\nthe name\nimprove\nand the name\nsqrt_iter\nitself refer to the right functions.\n\nEquipped with a way to handle declarations within blocks, we can\nrevisit declarations of names at the top level.\n\nIn\nsection , we saw\nthat the names declared at the top level are added to the program\nframe.\n\nA better explanation is that the whole program is placed in\nan implicit block, which is evaluated in the global environment.\n\nThe treatment of blocks described above then handles the top\nlevel:\nThe global environment is extended by a frame that contains the\nbindings of all names declared in the implicit block.\n\nThat frame is\nthe program frame and the resulting\nenvironment is the\n\nWe said that a block s body is evaluated in an environment that\ncontains all names declared directly in the body of the block.", + "token_count": 296, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": "Internal Declarations", + "chunk_index": 3, + "chunk_id": "Modularity_Objects_and_State_Internal_Declarations_3" + }, + { + "content": "We said that a block s body is evaluated in an environment that\ncontains all names declared directly in the body of the block.\n\nA locally declared name is put into the environment when the block is\nentered, but without an associated value.\n\nThe evaluation of its\ndeclaration during evaluation of the block body then assigns to the\nname the result of evaluating the expression to the right of the\n= , as if the declaration were\nan assignment.\n\nSince the addition of the name to the environment is\nseparate from the evaluation of the declaration, and the whole block\nis in the scope of the name, an erroneous program could attempt to", + "token_count": 113, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": "Internal Declarations", + "chunk_index": 4, + "chunk_id": "Modularity_Objects_and_State_Internal_Declarations_4" }, { - "chapter_file": "chapter5_chunks.json", - "section": "Computing with Register Machines", - "subsection": "Compilation", - "chunk_id": "chapter5_chunks_Compilation_1", + "content": "The overall specification of how the interpreter\nfunction application\nremains the same as when we first introduced it in\nsection :\n-\n-\nTo evaluate\nan application:\n-\n-\nEvaluate the subexpressions\nof the\napplication.\n-\n-\nApply the value of the\nfunction\nsubexpression\nto the values of the\nargument\nsubexpressions.\n\nThe environment model of evaluation replaces the substitution model in\nspecifying what it means to apply a compound\nfunction\nto arguments.\n\nIn the environment model of evaluation, a\nfunction\nis always a pair consisting of some code and a pointer to an environment.\n\nFunctions\nare created in one way only: by evaluating a\nlambda\nexpression.\nfunction\nwhose code is obtained from the text of the\nlambda\nexpression and whose environment is the environment in which the\nlambda\nexpression was evaluated to produce the\nfunction.\n\nFor example, consider the\nfunction declaration\n\n```javascript\nsquare_example\n 196\n\nfunction square(x) {\n return x * x;\n}\n```\n\nevaluated in the\nprogram\nenvironment.\n\nThe\nfunction declaration\nsyntax is\nequivalent to\nan underlying implicit\nlambda\nexpression.\n\nIt would have been equivalent to have used\n\n```javascript\nsquare_example\n 196\n\nconst square = x => x * x;\n```\n\nwhich evaluates\n\n```javascript\nx => x * x\n```\n\nand binds program environment.\n\nFigure shows the result of evaluating this declaration statement.\n\n```javascript\nThe global environment encloses the program environment. To reduce\n\tclutter, after this figure we will not display the global environment\n\t(as it is always the same), but we are reminded of its existence by the\n\tpointer from the program environment upward.\n```\n\nThe function object is a pair whose code specifies that the function has one parameter, namely function body\n\n```javascript\nreturn x * x;.\n```\n\nThe environment part of the\nfunction\nis a pointer to the program environment, since that is the environment in\nwhich the\nlambda\nexpression was evaluated to produce the\nfunction.", + "token_count": 308, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": "The Rules for Evaluation", "chunk_index": 1, - "content": "The explicit-control evaluator of section is a register machine whose controller interprets Scheme JavaScript programs. In this section we will see how to run Scheme JavaScript programs on a register machine whose controller is not a Scheme JavaScript interpreter. The explicit-control evaluator machine is it can carry out any computational process that can be described in Scheme. JavaScript. The evaluator s controller orchestrates the use of its data paths to perform the desired computation. Thus, the evaluator s data paths are universal: They are sufficient to perform any computation we desire, given an appropriate controller. Commercial native language of the machine, or simply machine language . Programs written in machine language are sequences of instructions that use the machine s data paths. For example, the s instruction sequence can be thought of as a machine-language program for a general-purpose computer rather than as the controller for a specialized interpreter machine. There are two common strategies for bridging the gap between higher-level languages and register-machine languages. The explicit-control evaluator illustrates the strategy of interpretation. An interpreter written in the native language of a machine configures the machine to execute programs written in a language (called the source language ) that may differ from the native language of the machine performing the evaluation. The primitive procedures functions of the source language are implemented as a library of subroutines written in the native language of the given machine. A program to be interpreted (called the source program ) is represented as a data structure. The interpreter traverses this data structure, analyzing the source program.", - "token_count": 297, - "source_files": [ - "section5.xml" - ] + "chunk_id": "Modularity_Objects_and_State_The_Rules_for_Evaluation_1" }, { - "chapter_file": "chapter5_chunks.json", - "section": "Computing with Register Machines", - "subsection": "Compilation", - "chunk_id": "chapter5_chunks_Compilation_2", + "content": "The environment part of the\nfunction\nis a pointer to the program environment, since that is the environment in\nwhich the\nlambda\nexpression was evaluated to produce the\nfunction.\n\nA new binding, which associates the\nfunction\nobject with the\nname\n\n```javascript\nEnvironment structure produced by evaluating\n\t function square(x) { return x * x; }\n\t in the program environment.\n```\n\nIn general, const ,\nfunction , and\nlet\nadd bindings to frames.\n\nAssignment is forbidden on constants, so our environment model\nneeds to distinguish names that refer to constants\nfrom names that refer to variables.\n\nWe indicate that\na name is a constant by writing an equal sign after the colon\nthat follows the name.\n\nWe consider function declarations as equivalent to constant\ndeclarations;.\n\nNow that we have seen how\nfunctions\nare created, we can describe how\nfunctions\nare applied.\n\nThe environment model specifies: To apply a\nfunction\nto arguments, create a new environment containing a frame that binds the\nparameters to the values of the arguments.\n\nThe enclosing environment of\nthis frame is the environment specified by the\nfunction.\n\nNow, within this new environment, evaluate the\nfunction\nbody.\n\nTo show how this rule is followed,\nfigure\nillustrates the environment structure created by evaluating the\nexpression\nsquare(5)\nin the\nprogram\nenvironment, where\nfunction\ngenerated in\nfigure.\n\nApplying the\nfunction\nresults in the creation of a new environment, labeled E1 in the figure, that\nbegins with a frame in which\nparameter for the\nfunction,\nis bound to the argument 5.\n\nNote that name\nThe pointer leading upward from this frame shows that the\nframe s enclosing environment is the\nprogram\nenvironment.\n\nThe\nprogram\nenvironment is chosen here, because this is the environment that is\nindicated as part of the\nfunction\nobject.\n\nWithin E1, we evaluate the body of the\nfunction,\n\n```javascript\nreturn x * x;.\n```", + "token_count": 304, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": "The Rules for Evaluation", "chunk_index": 2, - "content": "As it does so, it simulates the intended behavior of the source program by calling appropriate primitive subroutines from the library. In this section, we explore the alternative strategy of compilation . A compiler for a given source language and machine translates a source program into an equivalent program (called the object program ) written in the machine s native language. The compiler that we implement in this section translates programs written in Scheme JavaScript into sequences of instructions to be executed using the explicit-control evaluator machine s data paths. Compared with interpretation, compilation can provide a great increase in the efficiency of program execution, as we will explain below in the overview of the compiler. On the other hand, an interpreter provides a more powerful environment for interactive program development and debugging, because the source program being executed is available at run time to be examined and modified. In addition, because the entire library of primitives is present, new programs can be constructed and added to the system during debugging. In view of the complementary advantages of compilation and interpretation, modern program-development environments pursue a mixed strategy. Lisp interpreters These systems are generally organized so that interpreted procedures functions and compiled procedures functions can call each other. This enables a programmer to compile those parts of a program that are assumed to be debugged, thus gaining the efficiency advantage of compilation, while retaining the interpretive mode of execution for those parts of the program that are in the flux of interactive development and debugging.", - "token_count": 284, - "source_files": [ - "section5.xml" - ] + "chunk_id": "Modularity_Objects_and_State_The_Rules_for_Evaluation_2" }, { - "chapter_file": "chapter5_chunks.json", - "section": "Computing with Register Machines", - "subsection": "Compilation", - "chunk_id": "chapter5_chunks_Compilation_3", + "content": "Within E1, we evaluate the body of the\nfunction,\n\nSince the value of 5 * 5, or 25.\n\n```javascript\nEnvironment created by evaluating\n\t square(5)\n\t in the program environment.\n```\n\nThe environment model of\nfunction\napplication can be summarized by two\nrules:\n-\n-\nA\nfunction\nobject is applied to a set of arguments by constructing a frame,\nbinding the parameters of the function\nto the arguments of the call, and then evaluating the body of the\nfunction\nin the context of the new environment constructed.\n\nThe new frame has as\nits enclosing environment the environment part of the\nfunction\nobject being applied.\n\nThe result of the application is the result of evaluating\nthe return expression of the first return statement encountered\nwhile evaluating the function body.\n-\n-\nA\nfunction\nis created by evaluating a\nlambda\nexpression relative to a given environment.\n\nThe resulting\nfunction\nobject is a pair consisting of the text of the\nlambda\nexpression and a pointer to the environment in which the\nfunction\nwas created.\n\n```javascript\nname=value\n\tin some environment\n\tlocates the binding of the name in the environment.\n\tThat is, one finds the first frame in the environment that contains a\n\tbinding for the name.\n\tIf the binding is a variable bindingindicated in the frame by\n\tjust :\n\tafter the namethat binding is changed to reflect the new\n\tvalue of the variable.\n\tOtherwise, if the binding in the frame is a constant\n\tbindingindicated\n\tin the frame by :=\n\tafter the namethe assignment signals an\n\t\"assignment to constant\" error.\n\tIf the name is unbound in the environment, then\n\tthe assignment signals a\n\t\"variable undeclared\" error.\n```\n\nThese evaluation rules, though considerably more complex than the\nsubstitution model, are still reasonably straightforward.\n\nMoreover,\nthe evaluation model, though abstract, provides a correct description\nof how the interpreter evaluates expressions.", + "token_count": 301, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": "The Rules for Evaluation", "chunk_index": 3, - "content": "In section , after we have implemented the compiler, we will show how to interface it with our interpreter to produce an integrated interpreter-compiler development system. Our compiler is much like our interpreter, both in its structure and in the function it performs. Accordingly, the mechanisms used by the compiler for analyzing expressions components will be similar to those used by the interpreter. Moreover, to make it easy to interface compiled and interpreted code, we will design the compiler to generate code that obeys the same conventions of env register, argument lists will be accumulated in argl , a procedure function to be applied will be in proc , fun , procedures functions will return their answers in val , and the location to which a procedure function should return will be kept in continue . In general, the compiler translates a source program into an object program that performs essentially the same register operations as would the interpreter in evaluating the same source program. This description suggests a strategy for implementing a rudimentary compiler: We traverse the expression component in the same way the interpreter does. When we encounter a register instruction that the interpreter would perform in evaluating the expression, component, we do not execute the instruction but instead accumulate it into a sequence. The resulting sequence of instructions will be the object code.", - "token_count": 248, - "source_files": [ - "section5.xml" - ] + "chunk_id": "Modularity_Objects_and_State_The_Rules_for_Evaluation_3" }, { - "chapter_file": "chapter5_chunks.json", - "section": "Computing with Register Machines", - "subsection": "Compilation", - "chunk_id": "chapter5_chunks_Compilation_4", + "content": "Moreover,\nthe evaluation model, though abstract, provides a correct description\nof how the interpreter evaluates expressions.\n\nIn chapter we shall\nsee how this model can serve as a blueprint for implementing a working\ninterpreter.\n\nThe following sections elaborate the details of the\nmodel by analyzing some illustrative programs.", + "token_count": 48, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": "The Rules for Evaluation", "chunk_index": 4, - "content": "Observe the an expression for example, a component for example, (f 84 96) it f(96, 22) it performs the work of classifying the expression component (discovering that this is a procedure function application) and testing for the end of the operand list (discovering that there are two operands). list of argument expressions (discovering that there are two argument expressions). With a compiler, the expression component is analyzed only once, when the instruction sequence is generated at compile time. The object code produced by the compiler contains only the instructions that evaluate the operator and the two operands, function expression and the two argument expressions, assemble the argument list, and apply the procedure (in proc ) function (in fun ) to the arguments (in argl ). This is the same kind of optimization we implemented in the . But there are further opportunities to gain efficiency in compiled code. As the interpreter runs, it follows a process that must be applicable to any expression component in the language. In contrast, a given segment of compiled code is meant to execute some particular expression. component. This can make a big difference, for example in the use of the stack to save registers. When the interpreter evaluates an expression, a component, it must be prepared for any contingency. Before evaluating a subexpression, subcomponent, the interpreter saves all registers that will be needed later, because the subexpression subcomponent might require an arbitrary evaluation.", - "token_count": 290, - "source_files": [ - "section5.xml" - ] - }, - { - "chapter_file": "chapter5_chunks.json", - "section": "Computing with Register Machines", - "subsection": "Compilation", - "chunk_id": "chapter5_chunks_Compilation_5", - "chunk_index": 5, - "content": "A compiler, on the other hand, can exploit the structure of the particular expression component it is processing to generate code that avoids unnecessary stack operations. As a case in point, consider the combination (f 84 96) . application f(96, 22) . Before the interpreter evaluates the operator of the combination, function expression of the application, it prepares for this evaluation by saving the registers containing the operands argument expressions and the environment, whose values will be needed later. The interpreter then evaluates the operator function expression to obtain the result in val , restores the saved registers, and finally moves the result from val to proc . fun . However, in the particular expression we are dealing with, the operator function expression is the symbol name f , whose evaluation is accomplished by the machine operation lookup-variable-value , lookup_symbol_value , which does not alter any registers. The compiler that we implement in this section will take advantage of this fact and generate code that evaluates the operator function expression using the instruction (assign proc (op lookup-variable-value) (const f) (reg env)) assign(\"fun\", list(op(\"lookup_symbol_value\"), constant(\"f\"), reg(\"env\"))) where the argument to lookup_symbol_value is extracted at compile time from the parser's representation of f(96, 22) . This code not only avoids the unnecessary saves and restores but also assigns the value of the lookup directly to proc , fun , whereas the interpreter would obtain the result in val and then move this to proc .", - "token_count": 300, - "source_files": [ - "section5.xml" - ] - }, - { - "chapter_file": "chapter5_chunks.json", - "section": "Computing with Register Machines", - "subsection": "Compilation", - "chunk_id": "chapter5_chunks_Compilation_6", - "chunk_index": 6, - "content": "fun . A compiler can also optimize access to the environment. Having analyzed the code, the compiler can in many cases know in which frame a particular variable the value of a particular name will be located and access that frame directly, rather than performing the lookup-variable-value lookup_@symbol_@value search. We will discuss how to implement such variable access lexical addressing in section . Until then, however, we will focus on the kind of register and stack optimizations described above. There are many other optimizations that can be performed by a compiler, such as coding primitive operations in line instead of using a general apply mechanism (see exercise ); but we will not emphasize these here. Our main goal in this section is to illustrate the compilation process in a simplified (but still interesting) context.", - "token_count": 155, - "source_files": [ - "section5.xml" - ] + "chunk_id": "Modularity_Objects_and_State_The_Rules_for_Evaluation_4" } ] \ No newline at end of file From 310941ce650a10c5f197894d8316b26638222ed7 Mon Sep 17 00:00:00 2001 From: Isha Sovasaria Date: Fri, 7 Nov 2025 17:28:07 +0800 Subject: [PATCH 3/3] further edits to chunking logic --- parser/chunking.py | 72 +- parser/sicp_mesochunks_semantic_rag.json | 3362 +++++++++++++++------- 2 files changed, 2382 insertions(+), 1052 deletions(-) diff --git a/parser/chunking.py b/parser/chunking.py index 48347a4d1..da830c87f 100644 --- a/parser/chunking.py +++ b/parser/chunking.py @@ -46,6 +46,22 @@ SCHEME_PREFIXES = ("SCHEME",) CODE_BLOCK_TAGS = {"SNIPPET", "PROGRAMLISTING", "CODE", "DISPLAY"} INLINE_CODE_TAGS = {"JAVASCRIPTINLINE"} +SENTENCE_END_CHARS = { + ".", + "?", + "!", + "\"", + "'", + "\u00bb", + "\u201d", + "\u2019", + ":", + ";", + ")", + "]", + "}", + "`", +} def num_tokens(text: str) -> int: @@ -95,10 +111,23 @@ def normalize_code(text: str) -> str: return "\n".join(lines) +def _append_tail(parent: ET.Element, siblings: List[ET.Element], idx: int, tail: Optional[str]) -> None: + if not tail: + return + for prev_idx in range(idx - 1, -1, -1): + prev = siblings[prev_idx] + if prev in parent: + prev.tail = (prev.tail or "") + tail + return + parent.text = (parent.text or "") + tail + + def prune_tree(node: ET.Element) -> None: - for child in list(node): + children = list(node) + for idx, child in enumerate(children): tag = child.tag.upper() if tag in DROP_TAGS or tag.startswith(SCHEME_PREFIXES): + _append_tail(node, children, idx, child.tail) node.remove(child) continue prune_tree(child) @@ -111,8 +140,6 @@ def visit(el: ET.Element) -> None: tag = el.tag.upper() if tag in DROP_TAGS or tag.startswith(SCHEME_PREFIXES): return - if tag == "JAVASCRIPT_OUTPUT": - return if el.text: parts.append(el.text) for child in el: @@ -144,8 +171,9 @@ def flush_text() -> None: segments.append({"type": "text", "content": text}) buffer.clear() - def walk(el: ET.Element) -> None: + def walk(el: ET.Element, parent_tag: Optional[str] = None) -> None: tag = el.tag.upper() + parent_upper = parent_tag.upper() if parent_tag else None if tag in DROP_TAGS or tag.startswith(SCHEME_PREFIXES): return @@ -173,6 +201,12 @@ def walk(el: ET.Element) -> None: snippet = gather_code(el) if not snippet: return + inline_context = parent_upper not in CODE_BLOCK_TAGS + if inline_context: + inline_snippet = normalize_whitespace(snippet.replace("\n", " ")) + if inline_snippet: + append_text(inline_snippet) + return if "\n" in snippet or re.search(r"[;{}=]", snippet): flush_text() segments.append({"type": "code", "content": snippet}) @@ -184,7 +218,7 @@ def walk(el: ET.Element) -> None: append_text(el.text) for child in el: - walk(child) + walk(child, tag) if child.tail: append_text(child.tail) @@ -192,7 +226,7 @@ def walk(el: ET.Element) -> None: append_text(node.text) for child in node: - walk(child) + walk(child, node.tag) if child.tail: append_text(child.tail) @@ -349,6 +383,13 @@ def join_units(units: List[str], types: List[str]) -> str: return out.strip() +def has_sentence_ending(text: str) -> bool: + stripped = text.rstrip() + if not stripped: + return True + return stripped[-1] in SENTENCE_END_CHARS + + def chunk_units( units: List[Dict[str, str]], meta: Dict[str, Optional[str]], @@ -408,14 +449,23 @@ def flush() -> None: if carry_pending and not buffer: seed_carry() - if current_tokens + unit_tokens > MAX_TOKENS and buffer: + overflow = current_tokens + unit_tokens > MAX_TOKENS and buffer + allow_overflow = ( + overflow + and ttype == "code" + and types + and types[-1] == "text" + and not has_sentence_ending(buffer[-1]) + ) + + if overflow and not allow_overflow: flush() if carry_pending and not buffer: seed_carry() - - buffer.append(text) - types.append(ttype) - current_tokens += unit_tokens + else: + buffer.append(text) + types.append(ttype) + current_tokens = current_tokens + unit_tokens flush() return chunks diff --git a/parser/sicp_mesochunks_semantic_rag.json b/parser/sicp_mesochunks_semantic_rag.json index 704d5ce47..b6b5fd3f4 100644 --- a/parser/sicp_mesochunks_semantic_rag.json +++ b/parser/sicp_mesochunks_semantic_rag.json @@ -10,8 +10,8 @@ "chunk_id": "Building_Abstractions_with_Data_Building_Abstractions_with_Data_1" }, { - "content": "For the same\nreasons that we want compound\nfunctions:\nto elevate the conceptual level at which we can design our programs, to\nincrease the modularity of our designs, and to enhance the expressive power\nof our language.\n\nJust as the ability to\ndeclare functions\nenables us to deal with processes at a higher conceptual level than that of\nthe primitive operations of the language, the ability to construct compound\ndata objects enables us to deal with data at a higher conceptual level than\nthat of the primitive data objects of the language.\n\nConsider the task of designing a system to perform\nadd_rat\nthat takes two rational numbers and produces their sum.\n\nIn terms of\nsimple data, a rational number can be thought of as two integers: a\nnumerator and a denominator.\n\nThus, we could design a program in which\neach rational number would be represented by two integers (a numerator\nand a denominator) and where\nadd_rat\nwould be implemented by two\nfunctions\n(one producing the numerator of the sum and one producing\nthe denominator).\n\nBut this would be awkward, because we would then\nneed to explicitly keep track of which numerators corresponded to\nwhich denominators.\n\nIn a system intended to perform many operations\non many rational numbers, such bookkeeping details would clutter the\nprograms substantially, to say nothing of what they would do to our\nminds.\n\nIt would be much better if we could glue together\na numerator and denominator to form a pair a compound data\nobject that our programs could manipulate in a way that would\nbe consistent with regarding a rational number as a single conceptual\nunit.\n\nThe use of compound data also enables us to increase the modularity of\nour programs.", - "token_count": 285, + "content": "For the same\nreasons that we want compound\nfunctions:\nto elevate the conceptual level at which we can design our programs, to\nincrease the modularity of our designs, and to enhance the expressive power\nof our language.\n\nConsider the task of designing a system to perform\narithmetic with rational\nnumbers.\n\nWe could imagine an operation\nadd_rat\nthat takes two rational numbers and produces their sum.\n\nIn terms of\nsimple data, a rational number can be thought of as two integers: a\nnumerator and a denominator.\n\nThus, we could design a program in which\neach rational number would be represented by two integers (a numerator\nand a denominator) and where\nadd_rat\nwould be implemented by two\nfunctions\n(one producing the numerator of the sum and one producing\nthe denominator).\n\nBut this would be awkward, because we would then\nneed to explicitly keep track of which numerators corresponded to\nwhich denominators.\n\nIn a system intended to perform many operations\non many rational numbers, such bookkeeping details would clutter the\nprograms substantially, to say nothing of what they would do to our\nminds.\n\nIt would be much better if we could glue together\na numerator and denominator to form a pair a compound data\nobject that our programs could manipulate in a way that would\nbe consistent with regarding a rational number as a single conceptual\nunit.\n\nThe use of compound data also enables us to increase the modularity of\nour programs.\n\nIf we can manipulate rational numbers directly as\nobjects in their own right, then we can separate the part of our\nprogram that deals with rational numbers per se from the details of\nhow rational numbers may be represented as pairs of integers.", + "token_count": 282, "has_code": false, "chapter": "Building Abstractions with Data", "section": null, @@ -20,8 +20,8 @@ "chunk_id": "Building_Abstractions_with_Data_Building_Abstractions_with_Data_2" }, { - "content": "The use of compound data also enables us to increase the modularity of\nour programs.\n\nIf we can manipulate rational numbers directly as\nobjects in their own right, then we can separate the part of our\nprogram that deals with rational numbers per se from the details of\nhow rational numbers may be represented as pairs of integers.\n\nThe\ngeneral technique of isolating the parts of a program that deal with\nhow data objects are represented from the parts of a program that deal\nwith how data objects are used is a powerful design methodology called\ndata abstraction.\n\nWe will see how data abstraction makes\nprograms much easier to design, maintain, and modify.\n\nThe use of compound data leads to a real increase in the expressive power\nof our programming language.\n\nConsider the idea of forming a\nlinear combination $ax+by$.\n\nWe\nmight like to write a\nfunction\nthat would accept $a$ ,\n$b$ , $x$ , and\n$y$ as arguments and return the value of\n$ax+by$.\n\nThis presents no difficulty if the\narguments are to be numbers, because we can readily\ndeclare the function\n\n```javascript\nlinear_combination_example\n\nfunction linear_combination(a, b, x, y) {\n return a * x + b * y;\n}\n```\n\n```javascript\nlinear_combination_example\n\n\tlinear_combination(1, 2, 3, 4);\n```\n\nBut suppose we are not concerned only with numbers.\n\nSuppose we would like to\ndescribe a process that forms\nlinear combinations whenever addition and multiplication are\ndefined for rational numbers, complex numbers, polynomials, or\nwhatever.\n\nWe could express this as a\nfunction\nof the form\n\n```javascript\nfunction linear_combination(a, b, x, y) {\n return add(mul(a, x), mul(b, y));\n}\n```\n\nwhere functions linear_combination should need to know about functions function linear_combination, it is irrelevant what function such as linear_combination to pass its arguments along to\n\nWe begin this chapter by implementing the rational-number arithmetic system\nmentioned above.", - "token_count": 305, + "content": "If we can manipulate rational numbers directly as\nobjects in their own right, then we can separate the part of our\nprogram that deals with rational numbers per se from the details of\nhow rational numbers may be represented as pairs of integers.\n\nWe will see how data abstraction makes\nprograms much easier to design, maintain, and modify.\n\nThe use of compound data leads to a real increase in the expressive power\nof our programming language.\n\nConsider the idea of forming a\nlinear combination $ax+by$.\n\nWe\nmight like to write a\nfunction\nthat would accept $a$ ,\n$b$ , $x$ , and\n$y$ as arguments and return the value of\n$ax+by$.\n\nThis presents no difficulty if the\narguments are to be numbers, because we can readily\ndeclare the function\n\n```javascript\nlinear_combination_example\n\nfunction linear_combination(a, b, x, y) {\n return a * x + b * y;\n}\n```\n\n```javascript\nlinear_combination_example\n\n\tlinear_combination(1, 2, 3, 4);\n```\n\nBut suppose we are not concerned only with numbers.\n\nSuppose we would like to\ndescribe a process that forms\nlinear combinations whenever addition and multiplication are\ndefined for rational numbers, complex numbers, polynomials, or\nwhatever.\n\nWe could express this as a\nfunction\nof the form\n\n```javascript\nfunction linear_combination(a, b, x, y) {\n return add(mul(a, x), mul(b, y));\n}\n```\n\nwhere and\nare not the primitive\nfunctions\nand but rather\nmore complex things that will perform the appropriate operations for\nwhatever kinds of data we pass in as the arguments\n, ,\n, and.\n\nThe key\npoint is that the only thing\nlinear_combination\nshould need to know about ,\n, , and\nis that the\nfunctions\nand will\nperform the appropriate manipulations.", + "token_count": 275, "has_code": true, "chapter": "Building Abstractions with Data", "section": null, @@ -30,8 +30,8 @@ "chunk_id": "Building_Abstractions_with_Data_Building_Abstractions_with_Data_3" }, { - "content": "We begin this chapter by implementing the rational-number arithmetic system\nmentioned above.\n\nThis will form the background for our discussion of\ncompound data and data abstraction.\n\nAs with compound\nfunctions,\nthe main issue to be addressed is that of abstraction as a technique for\ncoping with complexity, and we will see how data abstraction enables us to\nerect suitable\nabstraction barriers\nbetween different parts of a program.\n\nWe will see that the key to forming compound data is that a programming\nlanguage should provide some kind of glue so that data\nobjects can be combined to form more complex data objects.\n\nThere are\nmany possible kinds of glue.\n\nIndeed, we will discover how to form compound\ndata using no special data operations at all, only\nfunctions.\n\nThis will further blur the distinction between\nfunction\nand data, which was already becoming tenuous toward the end\nof chapter.\n\nWe will also explore some conventional techniques for\nrepresenting sequences and trees.\n\nOne key idea in dealing with compound\ndata is the notion of\nclosure that the\nglue we use for combining data objects should allow us to combine not only\nprimitive data objects, but compound data objects as well.\n\nAnother key idea\nis that compound data objects can serve as\nconventional interfaces for combining program modules in\nmix-and-match ways.\n\nWe illustrate some of these ideas by presenting a\nsimple graphics language that exploits closure.\n\nWe will then augment the representational power of our language by\nintroducing\nsymbolic expressions data whose elementary parts\ncan be arbitrary symbols rather than only numbers.\n\nWe explore various\nalternatives for representing sets of objects.", - "token_count": 266, + "content": "The key\npoint is that the only thing\nlinear_combination\nshould need to know about ,\n, , and\nis that the\nfunctions\nand will\nperform the appropriate manipulations.\n\nThis same example\nshows why it is important that our programming language provide the ability\nto manipulate compound objects directly: Without this, there is no way for a\nfunction\nsuch as\nlinear_combination\nto pass its arguments along to and\nwithout having to know their detailed\nstructure.\n\nWe begin this chapter by implementing the rational-number arithmetic system\nmentioned above.\n\nThis will form the background for our discussion of\ncompound data and data abstraction.\n\nAs with compound\nfunctions,\nthe main issue to be addressed is that of abstraction as a technique for\ncoping with complexity, and we will see how data abstraction enables us to\nerect suitable\nabstraction barriers\nbetween different parts of a program.\n\nWe will see that the key to forming compound data is that a programming\nlanguage should provide some kind of glue so that data\nobjects can be combined to form more complex data objects.\n\nThere are\nmany possible kinds of glue.\n\nIndeed, we will discover how to form compound\ndata using no special data operations at all, only\nfunctions.\n\nThis will further blur the distinction between\nfunction\nand data, which was already becoming tenuous toward the end\nof chapter.\n\nWe will also explore some conventional techniques for\nrepresenting sequences and trees.\n\nOne key idea in dealing with compound\ndata is the notion of\nclosure that the\nglue we use for combining data objects should allow us to combine not only\nprimitive data objects, but compound data objects as well.\n\nAnother key idea\nis that compound data objects can serve as\nconventional interfaces for combining program modules in\nmix-and-match ways.", + "token_count": 290, "has_code": false, "chapter": "Building Abstractions with Data", "section": null, @@ -40,8 +40,8 @@ "chunk_id": "Building_Abstractions_with_Data_Building_Abstractions_with_Data_4" }, { - "content": "We explore various\nalternatives for representing sets of objects.\n\nWe will find that,\njust as a given numerical function can be computed by many different\ncomputational processes, there are many ways in which a given data\nstructure can be represented in terms of simpler objects, and the\nchoice of representation can have significant impact on the time and\nspace requirements of processes that manipulate the data.\n\nWe will\ninvestigate these ideas in the context of symbolic differentiation,\nthe representation of sets, and the encoding of information.\n\nNext we will take up the problem of working with data that may be\nrepresented differently by different parts of a program.\n\nThis leads\nto the need to implement\ngeneric operations , which must handle many different types of data.\n\nMaintaining modularity in the presence of generic operations requires more\npowerful abstraction barriers than can be erected with simple data\nabstraction alone.\n\nIn particular, we introduce data-directed\nprogramming as a technique that allows individual data representations\nto be designed in isolation and then combined\nadditively (i.e., without modification).\n\nTo illustrate the power\nof this approach to system design, we close the chapter by applying what we\nhave learned to the implementation of a package for performing symbolic\narithmetic on polynomials, in which the coefficients of the polynomials can\nbe integers, rational numbers, complex numbers, and even other polynomials.", - "token_count": 224, + "content": "Another key idea\nis that compound data objects can serve as\nconventional interfaces for combining program modules in\nmix-and-match ways.\n\nWe will then augment the representational power of our language by\nintroducing\nsymbolic expressions data whose elementary parts\ncan be arbitrary symbols rather than only numbers.\n\nWe explore various\nalternatives for representing sets of objects.\n\nWe will find that,\njust as a given numerical function can be computed by many different\ncomputational processes, there are many ways in which a given data\nstructure can be represented in terms of simpler objects, and the\nchoice of representation can have significant impact on the time and\nspace requirements of processes that manipulate the data.\n\nWe will\ninvestigate these ideas in the context of symbolic differentiation,\nthe representation of sets, and the encoding of information.\n\nNext we will take up the problem of working with data that may be\nrepresented differently by different parts of a program.\n\nThis leads\nto the need to implement\ngeneric operations , which must handle many different types of data.\n\nMaintaining modularity in the presence of generic operations requires more\npowerful abstraction barriers than can be erected with simple data\nabstraction alone.\n\nIn particular, we introduce data-directed\nprogramming as a technique that allows individual data representations\nto be designed in isolation and then combined\nadditively (i.e., without modification).\n\nTo illustrate the power\nof this approach to system design, we close the chapter by applying what we\nhave learned to the implementation of a package for performing symbolic\narithmetic on polynomials, in which the coefficients of the polynomials can\nbe integers, rational numbers, complex numbers, and even other polynomials.", + "token_count": 270, "has_code": false, "chapter": "Building Abstractions with Data", "section": null, @@ -50,9 +50,9 @@ "chunk_id": "Building_Abstractions_with_Data_Building_Abstractions_with_Data_5" }, { - "content": "As we have seen, pairs provide a primitive glue that we can\nuse to construct compound data objects.\n\nFigure\nshows a standard way to visualize a\nin this case, the pair formed by\npair(1, 2).\n\n```javascript\nIn this representation, which is called\n\tbox-and-pointer\n notation, each compound object is shown as a\n\tpointer to a box. The box for a pair\n\thas two parts, the left part containing the head of the pair and the\n\tright part containing the tail.\n```\n\nWe have already seen that\npair\ncan be used to combine not only numbers but pairs as well.\n\n(You made use\nof this fact, or should have, in doing\nexercises\nand.) As a consequence, pairs provide\na universal building block from which we can construct all sorts of data\nstructures.\n\nFigure\nshows two ways to use pairs to combine the numbers 1, 2, 3, and 4.\n\nTwo ways to combine 1, 2, 3, and 4 using pairs.\n\nThe ability to create pairs whose elements are pairs is the essence of\nlist structure s importance as a representational tool.\n\nWe refer to\nthis ability as the\nclosure property of\npair.\n\nIn general, an operation for combining data objects satisfies the closure\nproperty if the results of combining things with that operation can\nthemselves be combined using the same operation. hierarchical structures structures made up of parts, which\nthemselves are made up of parts, and so on.\n\nFrom the outset of chapter , we ve made essential use of\nclosure in dealing with\nfunctions,\nbecause all but the very simplest programs rely on the fact that the\nelements of a combination can themselves be combinations.\n\nIn this section,\nwe take up the consequences of closure for compound data.", - "token_count": 286, - "has_code": true, + "content": "As we have seen, pairs provide a primitive glue that we can\nuse to construct compound data objects.\n\nFigure\nshows a standard way to visualize a\npair in this case, the pair formed by\npair(1, 2).\n\nIn this representation, which is called box-and-pointer notation, each compound object is shown as a pointer to a box.\n\nThe box for a pair has two parts, the left part containing the head of the pair and the right part containing the tail.\n\nWe have already seen that\npair\ncan be used to combine not only numbers but pairs as well.\n\n(You made use\nof this fact, or should have, in doing\nexercises\nand.) As a consequence, pairs provide\na universal building block from which we can construct all sorts of data\nstructures.\n\nFigure\nshows two ways to use pairs to combine the numbers 1, 2, 3, and 4.\n\nTwo ways to combine 1, 2, 3, and 4 using pairs.\n\nThe ability to create pairs whose elements are pairs is the essence of\nlist structure s importance as a representational tool.\n\nWe refer to\nthis ability as the\nclosure property of\npair.\n\nIn general, an operation for combining data objects satisfies the closure\nproperty if the results of combining things with that operation can\nthemselves be combined using the same operation.\n\nClosure is the key to power in any means of combination because it permits\nus to create\nhierarchical structures structures made up of parts, which\nthemselves are made up of parts, and so on.\n\nFrom the outset of chapter , we ve made essential use of\nclosure in dealing with\nfunctions,\nbecause all but the very simplest programs rely on the fact that the\nelements of a combination can themselves be combinations.", + "token_count": 289, + "has_code": false, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", "subsection": null, @@ -60,8 +60,8 @@ "chunk_id": "Building_Abstractions_with_Data_Hierarchical_Data_and_the_Closure_Property_1" }, { - "content": "In this section,\nwe take up the consequences of closure for compound data.\n\nWe describe some\nconventional techniques for using pairs to represent sequences and trees,\nand we exhibit a graphics language that illustrates closure in a vivid\nway.", - "token_count": 39, + "content": "From the outset of chapter , we ve made essential use of\nclosure in dealing with\nfunctions,\nbecause all but the very simplest programs rely on the fact that the\nelements of a combination can themselves be combinations.\n\nWe describe some\nconventional techniques for using pairs to represent sequences and trees,\nand we exhibit a graphics language that illustrates closure in a vivid\nway.", + "token_count": 64, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -70,8 +70,8 @@ "chunk_id": "Building_Abstractions_with_Data_Hierarchical_Data_and_the_Closure_Property_2" }, { - "content": "This section presents a simple language for drawing pictures that\nillustrates the power of data abstraction and closure, and also exploits\nhigher-order\nfunctions\nin an essential way.\n\nThe language is designed to make it easy to\nexperiment with patterns such as the ones in\nfigure , which are composed of\nrepeated elements that are shifted and scaled.\nfunctions\nrather than as list structure.\n\nJust as\npair,\nwhich satisfies the\nDesigns generated with the picture language.\n\nWhen we began our study of programming in\nsection , we emphasized the\nimportance of describing a language by focusing on the language s\nprimitives, its means of combination, and its means of abstraction.\n\nWe ll follow that framework here.\n\nPart of the elegance of this picture language is that there is only one\nkind of element, called a\npainter.\n\nA painter draws an image that is shifted and scaled to\nfit within a designated\ns a primitive painter\nwe ll call.\n\nImages produced by the\nThe actual shape of the drawing depends on the frame all four\nimages in figure are produced by the same\ns founder, William Barton Rogers, as shown in\nfigure.\nare drawn with respect to the same four frames\nas the.\n\nImages of William Barton Rogers, founder and first\npresident of MIT, painted with respect to the same four frames as in\nfigure (original image courtesy MIT Museum).\n\nTo combine images, we use various\ns image\nin the left half of the frame and the second painter s image in the\nright half of the frame.\n\nSimilarly,\ns image below the\nsecond painter s image.\n\nSome operations transform a single painter\nto produce a new painter.", - "token_count": 276, + "content": "This section presents a simple language for drawing pictures that\nillustrates the power of data abstraction and closure, and also exploits\nhigher-order\nfunctions\nin an essential way.\n\nThe language is designed to make it easy to\nexperiment with patterns such as the ones in\nfigure , which are composed of\nrepeated elements that are shifted and scaled.\n\nIn this language, the data objects being\ncombined are represented as\nfunctions\nrather than as list structure.\n\nJust as\npair,\nwhich satisfies the\nclosure property, allowed us to easily build arbitrarily complicated list\nstructure, the operations in this language, which also satisfy the closure\nproperty, allow us to easily build arbitrarily complicated patterns.\n\nDesigns generated with the picture language.\n\nWhen we began our study of programming in\nsection , we emphasized the\nimportance of describing a language by focusing on the language s\nprimitives, its means of combination, and its means of abstraction.\n\nWe ll follow that framework here.\n\nPart of the elegance of this picture language is that there is only one\nkind of element, called a\npainter.\n\nA painter draws an image that is shifted and scaled to\nfit within a designated\nparallelogram-shaped frame.\n\nFor example, there s a primitive painter\nwe ll call\nthat makes a crude line drawing,\nas shown in figure.\n\nImages produced by the\npainter, with respect to four different frames.\n\nThe frames, shown\nwith dashed lines, are not part of the images.\n\nThe actual shape of the drawing depends on the frame all four\nimages in figure are produced by the same\npainter, but with respect to four\ndifferent frames.\n\nPainters can be more elaborate than this: The primitive\npainter called paints a picture of\nMIT s founder, William Barton Rogers, as shown in\nfigure.", + "token_count": 289, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -80,8 +80,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_1" }, { - "content": "Some operations transform a single painter\nto produce a new painter.\n\nFor example,\nflip_vert\ntakes a painter and produces a painter that draws its image upside-down, and\nflip_horiz\nproduces a painter that draws the original painter s image\nleft-to-right reversed.\n\nFigure shows the drawing of a painter called\n\n```javascript\nwave2\n wave2_example\n\nconst wave2 = beside(wave, flip_vert(wave));\nconst wave4 = below(wave2, wave2);\n\nconst heart2 = beside(heart, flip_vert(heart));\n// const heart4 = stack(heart2, heart2);\n```\n\n```javascript\nwave2_example\n\nwave2;\n\nshow(heart2);\n// show(heart4);\n```\n\nIn building up a complex image in this manner we are exploiting the fact\nthat painters are\ns means of combination.\n\nThe\npair,\nthe closure of our data under the means of combination is crucial to the\nability to create complex structures while using only a few operations.\n\nOnce we can combine painters, we would like to be able to abstract typical\npatterns of combining painters.\n\nWe will implement the painter operations as\nJavaScript functions.\n\nThis means that we don t need a special abstraction mechanism in the\npicture language: Since the means of combination are ordinary\nJavaScript functions,\nwe automatically have the capability to do anything with painter operations\nthat we can do with\nfunctions.\n\nFor example, we can abstract the pattern in\n\n```javascript\nflipped_pairs\n wave4_2\n\nfunction flipped_pairs(painter) {\n const painter2 = beside(painter, flip_vert(painter));\n return below(painter2, painter2);\n}\n\nfunction flipped_pairs(painter) {\n const painter2 = beside(painter, flip_vert(painter));\n return stack(painter2, painter2);\n}\n```\n\nand declare\n\n```javascript\nwave4_2\n flipped_pairs\n\nconst wave4 = flipped_pairs(wave);\n\nconst heart4 = flipped_pairs(heart);\nshow(heart4);\n```\n\n```javascript\nRecursive plans for\n\t right_split and\n\t corner_split.\n```\n\nWe can also define recursive operations.\n\nHere s one that makes\npainters split and branch towards the right as shown in\nfigures\nand\n:", - "token_count": 279, + "content": "Painters can be more elaborate than this: The primitive\npainter called paints a picture of\nMIT s founder, William Barton Rogers, as shown in\nfigure.\n\nImages of William Barton Rogers, founder and first\npresident of MIT, painted with respect to the same four frames as in\nfigure (original image courtesy MIT Museum).\n\nTo combine images, we use various\noperations that construct new painters\nfrom given painters.\n\nFor example, the\noperation takes two painters and\nproduces a new, compound painter that draws the first painter s image\nin the left half of the frame and the second painter s image in the\nright half of the frame.\n\nSimilarly,\ntakes two painters and produces a\ncompound painter that draws the first painter s image below the\nsecond painter s image.\n\nSome operations transform a single painter\nto produce a new painter.\n\nFor example,\nflip_vert\ntakes a painter and produces a painter that draws its image upside-down, and\nflip_horiz\nproduces a painter that draws the original painter s image\nleft-to-right reversed.\n\nFigure shows the drawing of a painter called that is built up in two stages starting from :\n\n```javascript\nwave2\n wave2_example\n\nconst wave2 = beside(wave, flip_vert(wave));\nconst wave4 = below(wave2, wave2);\n\nconst heart2 = beside(heart, flip_vert(heart));\n// const heart4 = stack(heart2, heart2);\n```\n\n```javascript\nwave2_example\n\nwave2;\n\nshow(heart2);\n// show(heart4);\n```\n\nIn building up a complex image in this manner we are exploiting the fact\nthat painters are\nclosed under the language s means of combination.\n\nThe or\nof two painters is itself a painter;\ntherefore, we can use it as an element in making more complex painters.\n\nAs with building up list structure using\npair,\nthe closure of our data under the means of combination is crucial to the\nability to create complex structures while using only a few operations.", + "token_count": 298, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -90,8 +90,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_2" }, { - "content": "Here s one that makes\npainters split and branch towards the right as shown in\nfigures\nand\n:\n\n```javascript\nright_split\n right_split_example_1\n\nfunction right_split(painter, n) {\n if (n === 0) {\n return painter;\n } else {\n const smaller = right_split(painter, n - 1);\n return beside(painter, below(smaller, smaller));\n }\n}\n\nfunction right_split(painter, n) {\n if (n === 0) {\n return painter;\n } else {\n const smaller = right_split(painter, n - 1);\n return beside(painter, stack(smaller, smaller));\n }\n}\n```\n\nWe can produce balanced patterns by branching upwards as well as towards the right (see exercise and figures and ):\n\n```javascript\ncorner_split\n right_split\n up_split\n corner_split_example_1\n\nfunction corner_split(painter, n) {\n if (n === 0) {\n return painter;\n } else {\n const up = up_split(painter, n - 1);\n const right = right_split(painter, n - 1);\n const top_left = beside(up, up);\n const bottom_right = below(right, right);\n const corner = corner_split(painter, n - 1);\n return beside(below(painter, top_left),\n below(bottom_right, corner));\n }\n}\n\nfunction corner_split(painter, n) {\n if (n === 0) {\n return painter;\n } else {\n const up = up_split(painter, n - 1);\n const right = right_split(painter, n - 1);\n const top_left = beside(up, up);\n const bottom_right = stack(right, right);\n const corner = corner_split(painter, n - 1);\n return stack(beside(top_left, corner),\n beside(painter, bottom_right));\n }\n}\n```\n\n```javascript\ncorner_split_example_1\n corner_split_example_1\n\nshow(corner_split(heart, 4));\n```\n\n```javascript\nright_split_example_1\n right_split\n\n right_split(wave, 4); // (a)\n right_split(rogers, 4); // (b)\n corner_split(wave, 4); // (c)\n corner_split(rogers, 4); // (d)\n\n show(right_split(heart, 4));\n```\n\n```javascript\nThe recursive operation\n right_split applied to the\n\t painters wave and\n rogers.\n Combining four\n corner_split figures\n\t produces symmetric\n\t square_limit\n as shown in figure.\n```\n\nBy placing four copies of a corner_split appropriately, we obtain a pattern called square_limit, whose application to :", - "token_count": 276, + "content": "As with building up list structure using\npair,\nthe closure of our data under the means of combination is crucial to the\nability to create complex structures while using only a few operations.\n\nWe will implement the painter operations as\nJavaScript functions.\n\nThis means that we don t need a special abstraction mechanism in the\npicture language: Since the means of combination are ordinary\nJavaScript functions,\nwe automatically have the capability to do anything with painter operations\nthat we can do with\nfunctions.\n\nFor example, we can abstract the pattern in\nas\n\n```javascript\nflipped_pairs\n wave4_2\n\nfunction flipped_pairs(painter) {\n const painter2 = beside(painter, flip_vert(painter));\n return below(painter2, painter2);\n}\n\nfunction flipped_pairs(painter) {\n const painter2 = beside(painter, flip_vert(painter));\n return stack(painter2, painter2);\n}\n```\n\nand declare as an instance of this pattern:\n\n```javascript\nwave4_2\n flipped_pairs\n\nconst wave4 = flipped_pairs(wave);\n\nconst heart4 = flipped_pairs(heart);\nshow(heart4);\n```\n\nRecursive plans for right_split and corner_split.\n\nWe can also define recursive operations.\n\nHere s one that makes\npainters split and branch towards the right as shown in\nfigures\nand\n:\n\n```javascript\nright_split\n right_split_example_1\n\nfunction right_split(painter, n) {\n if (n === 0) {\n return painter;\n } else {\n const smaller = right_split(painter, n - 1);\n return beside(painter, below(smaller, smaller));\n }\n}\n\nfunction right_split(painter, n) {\n if (n === 0) {\n return painter;\n } else {\n const smaller = right_split(painter, n - 1);\n return beside(painter, stack(smaller, smaller));\n }\n}\n```\n\nWe can produce balanced patterns by branching upwards as well as towards the right (see exercise and figures and ):", + "token_count": 249, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -100,8 +100,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_3" }, { - "content": "By placing four copies of a corner_split appropriately, we obtain a pattern called square_limit, whose application to :\n\n```javascript\nsquare_limit\n corner_split\n square_limit_example\n\nfunction square_limit(painter, n) {\n const quarter = corner_split(painter, n);\n const half = beside(flip_horiz(quarter), quarter);\n return below(flip_vert(half), half);\n}\n\nfunction square_limit(painter, n) {\n const quarter = corner_split(painter, n);\n const upper_half = beside(flip_horiz(quarter), quarter);\n const lower_half = beside(turn_upside_down(quarter),\n flip_vert(quarter));\n return stack(upper_half, lower_half);\n}\n```\n\n```javascript\nsquare_limit_example\n\nsquare_limit(rogers, 4);\n\nshow(square_limit(heart, 4));\n```\n\nIn addition to abstracting patterns of combining painters, we can work at a\nhigher level, abstracting patterns of combining painter operations.\n\nThat\nis, we can view the painter operations as elements to manipulate and can\nwrite means of combination for these\nelementsfunctions\nthat take painter operations as arguments and create new painter operations.\n\nFor example,\nflipped_pairs\nand\nsquare_limit\neach arrange four copies of a painter s image in a square pattern;\nthey differ only in how they orient the copies.\n\nOne way to abstract this\npattern of painter combination is with the following\nfunction,\nwhich takes four one-argument painter operations and produces a painter\noperation that transforms a given painter with those four operations and\narranges the results in a square.\n\nThe functions tl,\n\n```javascript\nsquare_of_four\n square_of_four_example\n\nfunction square_of_four(tl, tr, bl, br) {\n return painter => {\n const top = beside(tl(painter), tr(painter));\n const bottom = beside(bl(painter), br(painter));\n return below(bottom, top);\n };\n}\n\nfunction square_of_four(tl, tr, bl, br) {\n return painter => stack(beside(tl(painter), tr(painter)),\n beside(bl(painter), br(painter)));\n}\n```\n\n```javascript\nsquare_of_four_example\n identity\n\nsquare_of_four(flip_vert, identity,\n quarter_turn_right, quarter_turn_left)(rogers);\n\nshow(square_of_four(turn_upside_down, identity,\n quarter_turn_right, quarter_turn_left)\n (heart)\n );\n```\n\nThen\n\n```javascript\nflipped_pairs\n can be defined in terms of\n```\n\nsquare_of_four as follows:\n\n```javascript\nflipped_pairs_2\n square_of_four\n identity\n flipped_pairs_example\n\nfunction flipped_pairs(painter) {\n const combine4 = square_of_four(identity, flip_vert,\n identity, flip_vert);\n return combine4(painter);\n}\n\nfunction flipped_pairs(painter) {\n const combine4 = square_of_four(turn_upside_down, flip_vert,\n flip_horiz, identity);\n return combine4(painter);\n}\n```\n\n```javascript\nflipped_pairs_example\n\nflipped_pairs(rogers);\n\nshow(flipped_pairs(heart));\n```\n\nand square_limit can be expressed as", - "token_count": 308, + "content": "We can produce balanced patterns by branching upwards as well as towards the right (see exercise and figures and ):\n\n```javascript\ncorner_split_example_1\n corner_split_example_1\n\nshow(corner_split(heart, 4));\n```\n\n```javascript\nright_split_example_1\n right_split\n\n right_split(wave, 4); // (a)\n right_split(rogers, 4); // (b)\n corner_split(wave, 4); // (c)\n corner_split(rogers, 4); // (d)\n\n show(right_split(heart, 4));\n```\n\nThe recursive operation right_split applied to the painters wave and rogers.\n\nCombining four corner_split figures produces symmetric square_limit as shown in figure.\n\nBy placing four copies of a\ncorner_split\nappropriately, we obtain a pattern called\nsquare_limit,\nwhose application to and\nis shown in\nfigure :\n\n```javascript\nsquare_limit\n corner_split\n square_limit_example\n\nfunction square_limit(painter, n) {\n const quarter = corner_split(painter, n);\n const half = beside(flip_horiz(quarter), quarter);\n return below(flip_vert(half), half);\n}\n\nfunction square_limit(painter, n) {\n const quarter = corner_split(painter, n);\n const upper_half = beside(flip_horiz(quarter), quarter);\n const lower_half = beside(turn_upside_down(quarter),\n flip_vert(quarter));\n return stack(upper_half, lower_half);\n}\n```\n\n```javascript\nsquare_limit_example\n\nsquare_limit(rogers, 4);\n\nshow(square_limit(heart, 4));\n```\n\nIn addition to abstracting patterns of combining painters, we can work at a\nhigher level, abstracting patterns of combining painter operations.\n\nThat\nis, we can view the painter operations as elements to manipulate and can\nwrite means of combination for these\nelementsfunctions\nthat take painter operations as arguments and create new painter operations.\n\nFor example,\nflipped_pairs\nand\nsquare_limit\neach arrange four copies of a painter s image in a square pattern;\nthey differ only in how they orient the copies.\n\nOne way to abstract this\npattern of painter combination is with the following\nfunction,\nwhich takes four one-argument painter operations and produces a painter\noperation that transforms a given painter with those four operations and\narranges the results in a square.\n\nThe functions tl,\n, , and\nare the transformations to apply to the\ntop left copy, the top right copy, the bottom left copy, and the bottom\nright copy, respectively.", + "token_count": 297, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -110,8 +110,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_4" }, { - "content": "and square_limit can be expressed as\n\n```javascript\nsquare_limit_2\n square_of_four\n identity\n corner_split\n square_limit_example_2\n\nfunction square_limit(painter, n) {\n const combine4 = square_of_four(flip_horiz, identity,\n rotate180, flip_vert);\n return combine4(corner_split(painter, n));\n}\n\nfunction square_limit(painter, n) {\n const combine4 = square_of_four(flip_horiz, identity,\n turn_upside_down, flip_vert);\n return combine4(corner_split(painter, n));\n}\n```\n\n```javascript\nsquare_limit_example_2\n\nsquare_limit(rogers, 4)(full_frame);\n\nshow(square_limit(heart, 4));\n```\n\nBefore we can show how to implement painters and their means of\ncombination, we must first consider\nan origin vector\nand two edge vectors.\n\nThe origin vector specifies the offset of the\nframe s origin from some absolute origin in the plane, and the edge\nvectors specify the offsets of the frame s corners from its origin.\n\nIf the edges are perpendicular, the frame will be rectangular.\n\nOtherwise the frame will be a more general parallelogram.\n\nFigure shows a frame and its associated\nvectors.\n\nIn accordance with data abstraction, we need not be specific yet\nabout how frames are represented, other than to say that there is a\nconstructor\nmake_frame,\nwhich takes three vectors and produces a frame, and three corresponding\nselectors\norigin_frame,\nedge1_frame,\nand\nedge2_frame\n(see exercise ).\n\nA frame is described by three vectors an origin and two edges.\n\nWe will use coordinates in the\n$0\\leq x, y\\leq 1$ ) to specify images.\n\nWith\neach frame, we associate a\nframe coordinate map , which will be used to shift and scale images\nto fit the frame.\n\nThe map transforms the unit square into the frame by\nmapping the vector $\\mathbf{v}=(x, y)$ to the\nvector sum\n\\[\n\\text{Origin(Frame)} + x\\cdot \\text{ Edge}_1\\text{ (Frame)}\n+ y\\cdot \\text{ Edge}_2\\text{ (Frame)}\n\\]\nFor example, $(0, 0)$ is mapped to the origin of\nthe frame, $(1, 1)$ to the vertex diagonally\nopposite the origin, and $(0.5, 0.5)$ to the\ncenter of the frame.\n\nWe can create a frame s coordinate map with\nthe following\nfunction :", - "token_count": 303, + "content": "The functions tl,\n, , and\nare the transformations to apply to the\ntop left copy, the top right copy, the bottom left copy, and the bottom\nright copy, respectively.\n\n```javascript\nsquare_of_four_example\n identity\n\nsquare_of_four(flip_vert, identity,\n quarter_turn_right, quarter_turn_left)(rogers);\n\nshow(square_of_four(turn_upside_down, identity,\n quarter_turn_right, quarter_turn_left)\n (heart)\n );\n```\n\nThen flipped_pairs can be defined in terms of square_of_four as follows:\n\n```javascript\nflipped_pairs_2\n square_of_four\n identity\n flipped_pairs_example\n\nfunction flipped_pairs(painter) {\n const combine4 = square_of_four(identity, flip_vert,\n identity, flip_vert);\n return combine4(painter);\n}\n\nfunction flipped_pairs(painter) {\n const combine4 = square_of_four(turn_upside_down, flip_vert,\n flip_horiz, identity);\n return combine4(painter);\n}\n```\n\n```javascript\nflipped_pairs_example\n\nflipped_pairs(rogers);\n\nshow(flipped_pairs(heart));\n```\n\nand square_limit can be expressed as\n\n```javascript\nsquare_limit_2\n square_of_four\n identity\n corner_split\n square_limit_example_2\n\nfunction square_limit(painter, n) {\n const combine4 = square_of_four(flip_horiz, identity,\n rotate180, flip_vert);\n return combine4(corner_split(painter, n));\n}\n\nfunction square_limit(painter, n) {\n const combine4 = square_of_four(flip_horiz, identity,\n turn_upside_down, flip_vert);\n return combine4(corner_split(painter, n));\n}\n```\n\n```javascript\nsquare_limit_example_2\n\nsquare_limit(rogers, 4)(full_frame);\n\nshow(square_limit(heart, 4));\n```\n\nBefore we can show how to implement painters and their means of\ncombination, we must first consider\nframes.\n\nA frame can be described by three vectors an origin vector\nand two edge vectors.\n\nThe origin vector specifies the offset of the\nframe s origin from some absolute origin in the plane, and the edge\nvectors specify the offsets of the frame s corners from its origin.\n\nIf the edges are perpendicular, the frame will be rectangular.\n\nOtherwise the frame will be a more general parallelogram.\n\nFigure shows a frame and its associated\nvectors.\n\nIn accordance with data abstraction, we need not be specific yet\nabout how frames are represented, other than to say that there is a\nconstructor\nmake_frame,\nwhich takes three vectors and produces a frame, and three corresponding\nselectors\norigin_frame,\nedge1_frame,\nand\nedge2_frame\n(see exercise ).\n\nA frame is described by three vectors an origin and two edges.\n\nWe will use coordinates in the\nunit square\n( $0\\leq x, y\\leq 1$ ) to specify images.", + "token_count": 309, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -120,8 +120,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_5" }, { - "content": "We can create a frame s coordinate map with\nthe following\nfunction :\n\n```javascript\nframe_coord_map\n frame_coord_map_example\n [ 14, 18 ]\n frame_functions\n vector_functions\n\nfunction frame_coord_map(frame) {\n return v => add_vect(origin_frame(frame),\n add_vect(scale_vect(xcor_vect(v),\n edge1_frame(frame)),\n scale_vect(ycor_vect(v),\n edge2_frame(frame))));\n}\n```\n\n```javascript\nframe_coord_map_example\n\nconst my_origin = make_vect(1, 2);\nconst my_edge_1 = make_vect(3, 4);\nconst my_edge_2 = make_vect(5, 6);\nconst my_frame = make_frame(my_origin, my_edge_1, my_edge_2);\nconst my_coord_map = frame_coord_map(my_frame);\nconst my_vector = make_vect(1, 2);\nconst my_mapped_vector = my_coord_map(my_vector);\nmy_mapped_vector;\n```\n\nObserve that applying\nframe_coord_map\nto a frame returns a\nfunction\nthat, given a vector, returns a vector.\n\nIf the argument vector is in the\nunit square, the result vector will be in the frame.\n\nFor example,\n\n```javascript\nvector_example\n frame_functions\n vector_functions\n frame_coord_map\n [ 1, 2 ]\n\nframe_coord_map(a_frame)(make_vect(0, 0));\n\nconst a_frame = make_frame(make_vect(1, 2),\n make_vect(3, 4),\n make_vect(5, 5));\nframe_coord_map(a_frame)(make_vect(0, 0));\n```\n\nreturns the same vector as\n\n```javascript\nvector_example_2\n frame_functions\n [ 1, 2 ]\n\norigin_frame(a_frame);\n\nconst a_frame = make_frame(make_vect(1, 2),\n make_vect(3, 4),\n make_vect(5, 5));\norigin_frame(a_frame);\n```\n\nA painter is represented as a\nfunction\nthat, given a frame as argument, draws a particular image shifted and\nscaled to fit the frame.\n\nThat is to say, if\ns image in\n\nThe details of how primitive painters are implemented depend on the\nparticular characteristics of the graphics system and the type of image to\nbe drawn.\n\nFor instance, suppose we have a\nfunction\ndraw_line\nthat draws a line on the screen between two specified points.\n\nThen we can\ncreate painters for line drawings, such as the\nwave\npainter in figure , from lists of line\nsegments as follows:\n\n```javascript\ndraw_line\n\n// \"drawing a line\" here simulated\n// by printing the coordinates of\n// the start and end of the line\nfunction draw_line(v_start, v_end) {\n display(\"line starting at\");\n display(v_start);\n display(\"line ending at\");\n display(v_end);\n}\n```", - "token_count": 291, + "content": "We will use coordinates in the\nunit square\n( $0\\leq x, y\\leq 1$ ) to specify images.\n\nThe map transforms the unit square into the frame by\nmapping the vector $\\mathbf{v}=(x, y)$ to the\nvector sum\n\\[\n\\text{Origin(Frame)} + x\\cdot \\text{ Edge}_1\\text{ (Frame)}\n+ y\\cdot \\text{ Edge}_2\\text{ (Frame)}\n\\]\nFor example, $(0, 0)$ is mapped to the origin of\nthe frame, $(1, 1)$ to the vertex diagonally\nopposite the origin, and $(0.5, 0.5)$ to the\ncenter of the frame.\n\nWe can create a frame s coordinate map with\nthe following\nfunction :\n\n```javascript\nframe_coord_map\n frame_coord_map_example\n [ 14, 18 ]\n frame_functions\n vector_functions\n\nfunction frame_coord_map(frame) {\n return v => add_vect(origin_frame(frame),\n add_vect(scale_vect(xcor_vect(v),\n edge1_frame(frame)),\n scale_vect(ycor_vect(v),\n edge2_frame(frame))));\n}\n```\n\n```javascript\nframe_coord_map_example\n\nconst my_origin = make_vect(1, 2);\nconst my_edge_1 = make_vect(3, 4);\nconst my_edge_2 = make_vect(5, 6);\nconst my_frame = make_frame(my_origin, my_edge_1, my_edge_2);\nconst my_coord_map = frame_coord_map(my_frame);\nconst my_vector = make_vect(1, 2);\nconst my_mapped_vector = my_coord_map(my_vector);\nmy_mapped_vector;\n```\n\nObserve that applying\nframe_coord_map\nto a frame returns a\nfunction\nthat, given a vector, returns a vector.\n\nIf the argument vector is in the\nunit square, the result vector will be in the frame.\n\nFor example,\n\n```javascript\nvector_example\n frame_functions\n vector_functions\n frame_coord_map\n [ 1, 2 ]\n\nframe_coord_map(a_frame)(make_vect(0, 0));\n\nconst a_frame = make_frame(make_vect(1, 2),\n make_vect(3, 4),\n make_vect(5, 5));\nframe_coord_map(a_frame)(make_vect(0, 0));\n```\n\nreturns the same vector as\n\n```javascript\nvector_example_2\n frame_functions\n [ 1, 2 ]\n\norigin_frame(a_frame);\n\nconst a_frame = make_frame(make_vect(1, 2),\n make_vect(3, 4),\n make_vect(5, 5));\norigin_frame(a_frame);\n```\n\nA painter is represented as a\nfunction\nthat, given a frame as argument, draws a particular image shifted and\nscaled to fit the frame.\n\nThat is to say, if\nis a painter and\nis a frame, then we produce\ns image in\nby calling\nwith as argument.\n\nThe details of how primitive painters are implemented depend on the\nparticular characteristics of the graphics system and the type of image to\nbe drawn.", + "token_count": 307, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -130,8 +130,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_6" }, { - "content": "Then we can\ncreate painters for line drawings, such as the\nwave\npainter in figure , from lists of line\nsegments as follows:\n\n```javascript\nsegments_to_painter\n frame_coord_map\n segment_functions\n draw_line\n segments_to_painter_example\n\nfunction segments_to_painter(segment_list) {\n return frame =>\n for_each(segment =>\n draw_line(\n frame_coord_map(frame)\n (start_segment(segment)),\n frame_coord_map(frame)\n (end_segment(segment))),\n segment_list);\n}\n```\n\n```javascript\nsegments_to_painter_example\n\nconst my_origin = make_vect(1, 2);\nconst my_edge_1 = make_vect(3, 4);\nconst my_edge_2 = make_vect(5, 6);\nconst my_frame = make_frame(my_origin, my_edge_1, my_edge_2);\n\nconst my_start_1 = make_vect(0, 1);\nconst my_end_1 = make_vect(1, 1);\nconst my_segment_1 = make_segment(my_start_1, my_end_1);\n\nconst my_start_2 = make_vect(0, 2);\nconst my_end_2 = make_vect(2, 2);\nconst my_segment_2 = make_segment(my_start_2, my_end_2);\n\nconst my_painter = segments_to_painter(\n list(my_segment_1, my_segment_2));\n\nmy_painter(my_frame);\n```\n\nThe segments are given using coordinates with respect to the unit square.\n\nFor each segment in the list, the painter transforms the segment endpoints\nwith the frame coordinate map and draws a line between the transformed\npoints.\n\nRepresenting painters as\nfunctions\nerects a powerful abstraction barrier in the picture language.\n\nWe can\ncreate and intermix all sorts of primitive painters, based on a variety of\ngraphics capabilities.\n\nThe details of their implementation do not matter.\n\nAny\nfunction\ncan serve as a painter, provided that it takes a frame as argument and\ndraws something scaled to fit the frame.\n\nAn operation on painters (such as flip_vert or flip_vert doesn t have to know how a painter works in order to flip it it just\n\nhas to know how to turn a frame upside down: The flipped painter just uses the original painter, but in the inverted frame.\n\nPainter operations are based on the\nfunction\ntransform_painter,\nwhich takes as arguments a painter and information on how to transform a\nframe and produces a new painter.\n\nThe transformed painter, when called on\na frame, transforms the frame and calls the original painter on the\ntransformed frame.", - "token_count": 299, + "content": "The details of how primitive painters are implemented depend on the\nparticular characteristics of the graphics system and the type of image to\nbe drawn.\n\nThen we can\ncreate painters for line drawings, such as the\nwave\npainter in figure , from lists of line\nsegments as follows:\n\n```javascript\ndraw_line\n\n// \"drawing a line\" here simulated\n// by printing the coordinates of\n// the start and end of the line\nfunction draw_line(v_start, v_end) {\n display(\"line starting at\");\n display(v_start);\n display(\"line ending at\");\n display(v_end);\n}\n```\n\n```javascript\nsegments_to_painter\n frame_coord_map\n segment_functions\n draw_line\n segments_to_painter_example\n\nfunction segments_to_painter(segment_list) {\n return frame =>\n for_each(segment =>\n draw_line(\n frame_coord_map(frame)\n (start_segment(segment)),\n frame_coord_map(frame)\n (end_segment(segment))),\n segment_list);\n}\n```\n\n```javascript\nsegments_to_painter_example\n\nconst my_origin = make_vect(1, 2);\nconst my_edge_1 = make_vect(3, 4);\nconst my_edge_2 = make_vect(5, 6);\nconst my_frame = make_frame(my_origin, my_edge_1, my_edge_2);\n\nconst my_start_1 = make_vect(0, 1);\nconst my_end_1 = make_vect(1, 1);\nconst my_segment_1 = make_segment(my_start_1, my_end_1);\n\nconst my_start_2 = make_vect(0, 2);\nconst my_end_2 = make_vect(2, 2);\nconst my_segment_2 = make_segment(my_start_2, my_end_2);\n\nconst my_painter = segments_to_painter(\n list(my_segment_1, my_segment_2));\n\nmy_painter(my_frame);\n```\n\nThe segments are given using coordinates with respect to the unit square.\n\nFor each segment in the list, the painter transforms the segment endpoints\nwith the frame coordinate map and draws a line between the transformed\npoints.\n\nRepresenting painters as\nfunctions\nerects a powerful abstraction barrier in the picture language.\n\nWe can\ncreate and intermix all sorts of primitive painters, based on a variety of\ngraphics capabilities.\n\nThe details of their implementation do not matter.\n\nAny\nfunction\ncan serve as a painter, provided that it takes a frame as argument and\ndraws something scaled to fit the frame.\n\nAn operation on painters (such as\nflip_vert\nor )\nworks by creating a painter that invokes the original painters with respect\nto frames derived from the argument frame.", + "token_count": 293, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -140,8 +140,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_7" }, { - "content": "The transformed painter, when called on\na frame, transforms the frame and calls the original painter on the\ntransformed frame.\n\nThe arguments to\ntransform_painter\nare points (represented as vectors) that specify the corners of the new\nframe: When mapped into the frame, the first point specifies the new\nframe s origin and the other two specify the ends of its edge vectors.\n\nThus, arguments within the unit square specify a frame contained within the\noriginal frame.\n\n```javascript\ntransform_painter\n frame_functions\n vector_functions\n frame_coord_map\n flip_vert_example\n\nfunction transform_painter(painter, origin, corner1, corner2) {\n return frame => {\n const m = frame_coord_map(frame);\n const new_origin = m(origin);\n return painter(make_frame(\n new_origin,\n sub_vect(m(corner1), new_origin),\n sub_vect(m(corner2), new_origin)));\n };\n}\n```\n\nHere s how to flip painter images vertically:\n\n```javascript\nflip_vert\n transform_painter\n flip_vert_example\n\nfunction flip_vert(painter) {\n return transform_painter(painter,\n make_vect(0, 1), // new origin\n make_vect(1, 1), // new end of edge1\n make_vect(0, 0)); // new end of edge2\n}\n```\n\n```javascript\nflip_vert_example\n outline_painter\n unit_frame\n flip_vert\n\nconst flipped_outline_painter =\n flip_vert(outline_painter);\n\nflipped_outline_painter(unit_frame);\n```\n\nUsing\n\n```javascript\ntransform_painter,\n we can easily define new transformations.\n For example, we can declare a\n painter that shrinks its image to the upper-right quarter of the frame\n it is given:\n```\n\n```javascript\nshrink_to_upper_right\n transform_painter\n shrink_to_upper_right_example\n\nfunction shrink_to_upper_right(painter) {\n return transform_painter(painter,\n make_vect(0.5, 0.5),\n make_vect(1, 0.5),\n make_vect(0.5, 1));\n}\n```\n\n```javascript\nshrink_to_upper_right_example\n outline_painter\n unit_frame\n\nconst shrunk_outline_painter =\n shrink_to_upper_right(outline_painter);\n\nshrunk_outline_painter(unit_frame);\n```\n\nOther transformations rotate images counterclockwise by 90 degrees\n\n```javascript\nrotate90\n transform_painter\n rotate90_example\n\nfunction rotate90(painter) {\n return transform_painter(painter,\n make_vect(1, 0),\n make_vect(1, 1),\n make_vect(0, 0));\n}\n```\n\n```javascript\nrotate90_example\n outline_painter\n unit_frame\n\nconst rotated_outline_painter =\n rotate90(outline_painter);\n\nrotated_outline_painter(unit_frame);\n```\n\nor squash images towards the center of the frame:\n\n```javascript\nsquash_inwards\n transform_painter\n squash_inwards_example\n\nfunction squash_inwards(painter) {\n return transform_painter(painter,\n make_vect(0, 0),\n make_vect(0.65, 0.35),\n make_vect(0.35, 0.65));\n}\n```\n\n```javascript\nsquash_inwards_example\n outline_painter\n unit_frame\n\nconst squashed_outline_painter =\n squash_inwards(outline_painter);\n\nsquashed_outline_painter(unit_frame);\n```\n\nFrame transformation is also the key to\ndefining means of combining two or more painters.", - "token_count": 303, + "content": "An operation on painters (such as\nflip_vert\nor )\nworks by creating a painter that invokes the original painters with respect\nto frames derived from the argument frame.\n\nPainter operations are based on the\nfunction\ntransform_painter,\nwhich takes as arguments a painter and information on how to transform a\nframe and produces a new painter.\n\nThe transformed painter, when called on\na frame, transforms the frame and calls the original painter on the\ntransformed frame.\n\nThe arguments to\ntransform_painter\nare points (represented as vectors) that specify the corners of the new\nframe: When mapped into the frame, the first point specifies the new\nframe s origin and the other two specify the ends of its edge vectors.\n\nThus, arguments within the unit square specify a frame contained within the\noriginal frame.\n\n```javascript\ntransform_painter\n frame_functions\n vector_functions\n frame_coord_map\n flip_vert_example\n\nfunction transform_painter(painter, origin, corner1, corner2) {\n return frame => {\n const m = frame_coord_map(frame);\n const new_origin = m(origin);\n return painter(make_frame(\n new_origin,\n sub_vect(m(corner1), new_origin),\n sub_vect(m(corner2), new_origin)));\n };\n}\n```\n\nHere s how to flip painter images vertically:\n\n```javascript\nflip_vert\n transform_painter\n flip_vert_example\n\nfunction flip_vert(painter) {\n return transform_painter(painter,\n make_vect(0, 1), // new origin\n make_vect(1, 1), // new end of edge1\n make_vect(0, 0)); // new end of edge2\n}\n```\n\n```javascript\nflip_vert_example\n outline_painter\n unit_frame\n flip_vert\n\nconst flipped_outline_painter =\n flip_vert(outline_painter);\n\nflipped_outline_painter(unit_frame);\n```\n\nUsing\ntransform_painter, we can easily define new transformations.\n\nFor example, we can declare a painter that shrinks its image to the upper-right quarter of the frame it is given:\n\n```javascript\nshrink_to_upper_right\n transform_painter\n shrink_to_upper_right_example\n\nfunction shrink_to_upper_right(painter) {\n return transform_painter(painter,\n make_vect(0.5, 0.5),\n make_vect(1, 0.5),\n make_vect(0.5, 1));\n}\n```\n\n```javascript\nshrink_to_upper_right_example\n outline_painter\n unit_frame\n\nconst shrunk_outline_painter =\n shrink_to_upper_right(outline_painter);\n\nshrunk_outline_painter(unit_frame);\n```\n\nOther transformations rotate images counterclockwise by 90 degrees\n\n```javascript\nrotate90\n transform_painter\n rotate90_example\n\nfunction rotate90(painter) {\n return transform_painter(painter,\n make_vect(1, 0),\n make_vect(1, 1),\n make_vect(0, 0));\n}\n```\n\n```javascript\nrotate90_example\n outline_painter\n unit_frame\n\nconst rotated_outline_painter =\n rotate90(outline_painter);\n\nrotated_outline_painter(unit_frame);\n```\n\nor squash images towards the center of the frame:", + "token_count": 314, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -150,8 +150,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_8" }, { - "content": "Frame transformation is also the key to\ndefining means of combining two or more painters.\n\nThe\nfunction,\nfor example, takes two painters, transforms them to paint in the left and\nright halves of an argument frame respectively, and produces a new,\ncompound painter.\n\nWhen the compound painter is given a frame, it calls the\nfirst transformed painter to paint in the left half of the frame and calls\nthe second transformed painter to paint in the right half of the frame:\n\n```javascript\nbeside\n transform_painter\n beside_example\n\nfunction beside(painter1, painter2) {\n const split_point = make_vect(0.5, 0);\n const paint_left = transform_painter(painter1,\n make_vect(0, 0),\n split_point,\n make_vect(0, 1));\n const paint_right = transform_painter(painter2,\n split_point,\n make_vect(1, 0),\n make_vect(0.5, 1));\n return frame => {\n paint_left(frame);\n paint_right(frame);\n };\n}\n```\n\n```javascript\nbeside_example\n x_painter_example\n\nbeside(x_painter, x_painter)(unit_frame);\n```\n\nObserve how the painter data abstraction, and in particular the representation of painters as functions, makes function need not know anything about the details of\n\nthe component painters other than that each painter will draw something in its designated frame.\n\nThe picture language exploits some of the critical ideas we ve\nintroduced about abstraction with\nfunctions\nand data.\n\nThe fundamental data abstractions, painters, are implemented\nusing\nfunctional\nrepresentations, which enables the language to handle different basic\ndrawing capabilities in a uniform way.\n\nThe means of combination satisfy\nthe closure property, which permits us to easily build up complex designs.\n\nFinally, all the tools for abstracting\nfunctions\nare available to us for abstracting means of combination for painters.\n\nWe have also obtained a glimpse of another crucial idea about languages and\nprogram design.\n\nThis is the approach of\nstratified design , the notion that a complex system should be\nstructured as a sequence of levels that are described using a sequence of\nlanguages.", - "token_count": 289, + "content": "or squash images towards the center of the frame:\n\n```javascript\nsquash_inwards_example\n outline_painter\n unit_frame\n\nconst squashed_outline_painter =\n squash_inwards(outline_painter);\n\nsquashed_outline_painter(unit_frame);\n```\n\nFrame transformation is also the key to\ndefining means of combining two or more painters.\n\nThe\nfunction,\nfor example, takes two painters, transforms them to paint in the left and\nright halves of an argument frame respectively, and produces a new,\ncompound painter.\n\nWhen the compound painter is given a frame, it calls the\nfirst transformed painter to paint in the left half of the frame and calls\nthe second transformed painter to paint in the right half of the frame:\n\n```javascript\nbeside\n transform_painter\n beside_example\n\nfunction beside(painter1, painter2) {\n const split_point = make_vect(0.5, 0);\n const paint_left = transform_painter(painter1,\n make_vect(0, 0),\n split_point,\n make_vect(0, 1));\n const paint_right = transform_painter(painter2,\n split_point,\n make_vect(1, 0),\n make_vect(0.5, 1));\n return frame => {\n paint_left(frame);\n paint_right(frame);\n };\n}\n```\n\n```javascript\nbeside_example\n x_painter_example\n\nbeside(x_painter, x_painter)(unit_frame);\n```\n\nObserve how the painter data abstraction, and in particular the\nrepresentation of painters as\nfunctions,\nmakes\neasy to implement.\n\nThe\nfunction\nneed not know anything about the details of the component painters other\nthan that each painter will draw something in its designated frame.\n\nThe picture language exploits some of the critical ideas we ve\nintroduced about abstraction with\nfunctions\nand data.\n\nThe fundamental data abstractions, painters, are implemented\nusing\nfunctional\nrepresentations, which enables the language to handle different basic\ndrawing capabilities in a uniform way.\n\nThe means of combination satisfy\nthe closure property, which permits us to easily build up complex designs.\n\nFinally, all the tools for abstracting\nfunctions\nare available to us for abstracting means of combination for painters.\n\nWe have also obtained a glimpse of another crucial idea about languages and\nprogram design.", + "token_count": 282, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -160,8 +160,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_9" }, { - "content": "This is the approach of\nstratified design , the notion that a complex system should be\nstructured as a sequence of levels that are described using a sequence of\nlanguages.\n\nEach level is constructed by combining parts that are regarded\nas primitive at that level, and the parts constructed at each level are\nused as primitives at the next level.\n\nThe language used at each level\nof a stratified design has primitives, means of combination, and means\nof abstraction appropriate to that level of detail.\n\nStratified design pervades the engineering of complex systems.\n\nFor\nexample, in computer engineering, resistors and transistors are\ncombined (and described using a language of analog circuits) to\nproduce parts such as and-gates and or-gates, which form the\nprimitives of a language for digital-circuit design.\n\nAs a tiny example of stratification, our picture language uses primitive elements (primitive painters) that specify points and lines to provide the shapes of a\n\npainter like square_of_four, capture common patterns of combining geometric combiners.\n\nStratified design helps make programs\nrobust , that is, it makes\nit likely that small changes in a specification will require\ncorrespondingly small changes in the program.\n\nFor instance, suppose we\nwanted to change the image based on.\n\nWe could work\nat the lowest level to change the detailed appearance of the\ncorner_split\nreplicates the\nsquare_limit\narranges the four copies of the corner.\n\nIn general, each level of a\nstratified design provides a different vocabulary for expressing the\ncharacteristics of the system, and a different kind of ability to change it.", - "token_count": 255, + "content": "We have also obtained a glimpse of another crucial idea about languages and\nprogram design.\n\nEach level is constructed by combining parts that are regarded\nas primitive at that level, and the parts constructed at each level are\nused as primitives at the next level.\n\nThe language used at each level\nof a stratified design has primitives, means of combination, and means\nof abstraction appropriate to that level of detail.\n\nStratified design pervades the engineering of complex systems.\n\nFor\nexample, in computer engineering, resistors and transistors are\ncombined (and described using a language of analog circuits) to\nproduce parts such as and-gates and or-gates, which form the\nprimitives of a language for digital-circuit design.\n\nThese parts are combined to build\nprocessors, bus structures, and memory systems, which are in turn\ncombined to form computers, using languages appropriate to computer\narchitecture.\n\nComputers are combined to form distributed systems,\nusing languages appropriate for describing network interconnections,\nand so on.\n\nAs a tiny example of stratification, our picture language uses primitive\nelements (primitive painters) that specify points and lines to provide the\nshapes of a painter like.\n\nThe bulk of\nour description of the picture language focused on combining these\nprimitives, using geometric combiners such as\nand.\n\nWe also worked at a higher level, regarding\nand\nas primitives to be manipulated in a language whose operations, such as\nsquare_of_four,\ncapture common patterns of combining geometric combiners.\n\nStratified design helps make programs\nrobust , that is, it makes\nit likely that small changes in a specification will require\ncorrespondingly small changes in the program.\n\nFor instance, suppose we\nwanted to change the image based on\nshown in figure.", + "token_count": 274, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -170,8 +170,18 @@ "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_10" }, { - "content": "The representation of sequences in terms of lists generalizes naturally to\nrepresent sequences whose elements may themselves be sequences.\n\nFor\nexample, we can regard the object\n[[1, [2, null]], [3, [4, null]]]\nconstructed by\n\n```javascript\npair(list(1, 2), list(3, 4));\n```\n\nas a list of three items, the first of which is itself a list,\n[1, [2, null]].\n\nFigure\nshows the representation of this structure in terms of pairs.\n\n```javascript\nStructure formed by\n\t pair(list(1, 2), list(3, 4)).\n```\n\nAnother way to think of sequences whose elements are sequences is as\ntrees.\n\nThe elements of the sequence are the branches of the\ntree, and elements that are themselves sequences are subtrees.\n\nFigure\nshows the structure in\nfigure\nviewed as a tree.\n\n```javascript\nThe list structure in\n\t figure viewed as a tree.\n```\n\nRecursion length function of section with the count_leaves function, which returns the total number of leaves of a tree:\n\n```javascript\ntree_x\n\nconst x = pair(list(1, 2), list(3, 4));\n```\n\n```javascript\nlength_tree_x\n tree_x\n 3\n\nlength(x);\n```\n\n```javascript\ncount_leaves_tree_x\n tree_x\n count_leaves\n 4\n\ncount_leaves(x);\n```\n\n```javascript\nlist_x_x\n tree_x\n 3\n\nlist(x, x);\n\nlength(head(tail(list(x, x))));\n```\n\n```javascript\nlength_list_x_x\n tree_x\n 2\n\nlength(list(x, x));\n```\n\n```javascript\ncount_leaves_list_x_x\n tree_x\n count_leaves\n 8\n\ncount_leaves(list(x, x));\n```\n\nTo implement count_leaves, recall the recursive plan for computing length: - - The length of a list the length of the tail of - -\n\nThe length of the empty list is 0.\n\n```javascript\nThe function\n count_leaves\n```\n\nis similar.\n\nThe value for the empty list is the same:\n-\n-\ncount_leaves\nof the empty list is 0.\n\nBut in the reduction step, where we strip off the\nhead\nof the list, we must take into account that the\nhead\nmay itself be a tree whose leaves we need to count.", - "token_count": 287, + "content": "For instance, suppose we\nwanted to change the image based on\nshown in figure.\n\nIn general, each level of a\nstratified design provides a different vocabulary for expressing the\ncharacteristics of the system, and a different kind of ability to change it.", + "token_count": 42, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Example: A Picture Language", + "chunk_index": 11, + "chunk_id": "Building_Abstractions_with_Data_Example_A_Picture_Language_11" + }, + { + "content": "The representation of sequences in terms of lists generalizes naturally to\nrepresent sequences whose elements may themselves be sequences.\n\nFor\nexample, we can regard the object\n[[1, [2, null]], [3, [4, null]]]\nconstructed by\n\n```javascript\npair(list(1, 2), list(3, 4));\n```\n\nas a list of three items, the first of which is itself a list,\n[1, [2, null]].\n\nFigure\nshows the representation of this structure in terms of pairs.\n\nStructure formed by pair(list(1, 2), list(3, 4)).\n\nAnother way to think of sequences whose elements are sequences is as\ntrees.\n\nThe elements of the sequence are the branches of the\ntree, and elements that are themselves sequences are subtrees.\n\nFigure\nshows the structure in\nfigure\nviewed as a tree.\n\nThe list structure in figure viewed as a tree.\n\nRecursion\nis a natural tool for dealing with tree structures, since we can\noften reduce operations on trees to operations on their branches, which\nreduce in turn to operations on the branches of the branches, and so on,\nuntil we reach the leaves of the tree.\n\nAs an example, compare the\nlength\nfunction\nof section with the\ncount_leaves\nfunction,\nwhich returns the total number of leaves of a tree:\n\n```javascript\ntree_x\n\nconst x = pair(list(1, 2), list(3, 4));\n```\n\n```javascript\nlength_tree_x\n tree_x\n 3\n\nlength(x);\n\n3\n```\n\n```javascript\ncount_leaves_tree_x\n tree_x\n count_leaves\n 4\n\ncount_leaves(x);\n\n4\n```\n\n```javascript\nlist_x_x\n tree_x\n 3\n\nlist(x, x);\n\nlength(head(tail(list(x, x))));\n\nlist(list(list(1, 2), 3, 4), list(list(1, 2), 3, 4))\n```\n\n```javascript\nlength_list_x_x\n tree_x\n 2\n\nlength(list(x, x));\n\n2\n```\n\n```javascript\ncount_leaves_list_x_x\n tree_x\n count_leaves\n 8\n\ncount_leaves(list(x, x));\n\n8\n```\n\nTo implement\ncount_leaves,\nrecall the recursive plan for computing\nlength:\n-\n-\nThe length\nof a list is 1 plus\nthe length\nof the\ntail\nof.\n-\n-\nThe length\nof the empty list is 0.\n\nThe function count_leaves\nis similar.\n\nThe value for the empty list is the same:\n-\n-\ncount_leaves\nof the empty list is 0.", + "token_count": 313, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -180,8 +190,8 @@ "chunk_id": "Building_Abstractions_with_Data_Hierarchical_Structures_1" }, { - "content": "But in the reduction step, where we strip off the\nhead\nof the list, we must take into account that the\nhead\nmay itself be a tree whose leaves we need to count.\n\nThus, the appropriate\nreduction step is\n-\n-\ncount_leaves\nof a tree\ncount_leaves\nof the\nhead\nof\ncount_leaves\nof the\ntail\nof\nFinally, by taking\nheads\nwe reach actual leaves, so we need another base case:\n-\n-\ncount_leaves\nof a leaf is 1.\n\nTo aid in writing recursive\nfunctions\non trees,\nour JavaScript environment\nprovides the primitive predicate\nis_pair,\nwhich tests whether its argument is a pair.\n\nHere is the complete\nfunction:\n\n```javascript\ncount_leaves\n count_leaves_example\n 4\n\nfunction count_leaves(x) {\n return is_null(x)\n ? 0\n : ! is_pair(x)\n ? 1\n : count_leaves(head(x)) + count_leaves(tail(x));\n}\n```\n\n```javascript\ncount_leaves_example\n\ncount_leaves(pair(list(1, 2), list(3, 4)));\n```\n\nJust as\nscale_tree\nfunction,\nanalogous to\nscale_list\nof section , takes as arguments a numeric\nfactor and a tree whose leaves are numbers.\n\nIt returns a tree of the same\nshape, where each number is multiplied by the factor.\n\nThe recursive plan\nfor\nscale_tree\nis similar to the one for\ncount_leaves:\n\n```javascript\nscale_tree\n scale_tree_example\n 10\n\nfunction scale_tree(tree, factor) {\n return is_null(tree)\n ? null\n : ! is_pair(tree)\n ? tree * factor\n : pair(scale_tree(head(tree), factor),\n scale_tree(tail(tree), factor));\n}\n```\n\n```javascript\nscale_tree_example\n scale_tree\n\nscale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)),\n 10);\n\nhead(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)),\n 10));\n```\n\nAnother way to implement\nscale_tree\nis to regard the tree as a sequence of sub-trees and use\nmap.\n\nWe map over the sequence, scaling each sub-tree in turn, and return the\nlist of results.\n\nIn the base case, where the tree is a leaf, we simply\nmultiply by the factor:\n\n```javascript\nscale_tree_example_2\n\nscale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)),\n 10);\n\nhead(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)),\n 10));\n```", - "token_count": 301, + "content": "The value for the empty list is the same:\n-\n-\ncount_leaves\nof the empty list is 0.\n\nThus, the appropriate\nreduction step is\n-\n-\ncount_leaves\nof a tree is\ncount_leaves\nof the\nhead\nof plus\ncount_leaves\nof the\ntail\nof.\n\nFinally, by taking\nheads\nwe reach actual leaves, so we need another base case:\n-\n-\ncount_leaves\nof a leaf is 1.\n\nTo aid in writing recursive\nfunctions\non trees,\nour JavaScript environment\nprovides the primitive predicate\nis_pair,\nwhich tests whether its argument is a pair.\n\nHere is the complete\nfunction:\n\n```javascript\ncount_leaves\n count_leaves_example\n 4\n\nfunction count_leaves(x) {\n return is_null(x)\n ? 0\n : ! is_pair(x)\n ? 1\n : count_leaves(head(x)) + count_leaves(tail(x));\n}\n```\n\n```javascript\ncount_leaves_example\n\ncount_leaves(pair(list(1, 2), list(3, 4)));\n```\n\nJust as is a powerful abstraction for\ndealing with sequences, together with\nrecursion is a powerful abstraction for dealing with trees.\n\nFor instance,\nthe\nscale_tree\nfunction,\nanalogous to\nscale_list\nof section , takes as arguments a numeric\nfactor and a tree whose leaves are numbers.\n\nIt returns a tree of the same\nshape, where each number is multiplied by the factor.\n\nThe recursive plan\nfor\nscale_tree\nis similar to the one for\ncount_leaves:\n\n```javascript\nscale_tree\n scale_tree_example\n 10\n\nfunction scale_tree(tree, factor) {\n return is_null(tree)\n ? null\n : ! is_pair(tree)\n ? tree * factor\n : pair(scale_tree(head(tree), factor),\n scale_tree(tail(tree), factor));\n}\n```\n\n```javascript\nscale_tree_example\n scale_tree\n\nscale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)),\n 10);\n\nhead(scale_tree(list(1, list(2, list(3, 4), 5), list(6, 7)),\n 10));\n\nlist(10, list(20, list(30, 40), 50), list(60, 70))\n```\n\nAnother way to implement\nscale_tree\nis to regard the tree as a sequence of sub-trees and use\nmap.\n\nWe map over the sequence, scaling each sub-tree in turn, and return the\nlist of results.\n\nIn the base case, where the tree is a leaf, we simply\nmultiply by the factor:", + "token_count": 298, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -200,8 +210,8 @@ "chunk_id": "Building_Abstractions_with_Data_Hierarchical_Structures_3" }, { - "content": "One of the useful structures we can build with pairs is a\nsequence an ordered collection of data objects.\n\nThere\nare, of course, many ways to represent sequences in terms of pairs.\n\nOne\nparticularly straightforward representation is illustrated in\nfigure,\nwhere the sequence 1, 2, 3, 4 is represented as a chain of pairs.\n\nThe\nhead\nof each pair is the\ncorresponding item in the chain, and the\ntail\nof the pair is the next pair in the chain.\n\nThe\ntail\nof the final pair signals the end of the\nsequence,\nrepresented in box-and-pointer\ndiagrams as a diagonal line\n\n```javascript\nJavaScripts primitive value\n\tnull.\n```\n\nThe entire sequence is constructed by nested pair operations:\n\n```javascript\ncons_example\n\npair(1,\n pair(2,\n pair(3,\n pair(4, null))));\n```\n\nSuch a sequence of pairs, formed by nested\npair applications,\nis called a\nlist , and\nour JavaScript environment\nprovides a primitive called\nlist(1, 2, 3, 4).\n\nIn general,\n\n```javascript\nlist(a$_{1}$, a$_{2}$, $\\ldots$, a$_{n}$)\n```\n\nis equivalent to\n\n```javascript\npair(a$_{1}$, pair(a$_{2}$, pair($\\ldots$, pair(a$_{n}$, null)$\\ldots$)))\n```\n\n```javascript\nOur interpreter prints pairs using a textual representation of\n\tbox-and-pointer diagrams that we call box notation.\n pair(1, 2)\n is printed as [1, 2], and\n\tthe data object in figure\n is printed as\n [1, [2, [3, [4, null]]]]:\n```\n\n```javascript\none_four\n one_four_example\n [ 2, [ 3, [ 4, null ] ] ]\n\nconst one_through_four = list(1, 2, 3, 4);\n```\n\n```javascript\none_four\n one_four_example\n\none_through_four;\n\ntail(one_through_four);\n```\n\nWe can think of\nhead\nas selecting the first item in the list, and of\ntail\nas selecting the sublist consisting of all but the first item.\n\nNested\napplications of\nhead\nand\ntail\ncan be used to extract the second, third, and subsequent items in the\nlist.\n\nThe constructor\npair\nmakes a list like the original one, but with an additional item at the\nbeginning.\n\n```javascript\ncar_one_four\n one_four\n 1\n\nhead(one_through_four);\n```", - "token_count": 304, + "content": "One of the useful structures we can build with pairs is a\nsequence an ordered collection of data objects.\n\nThere\nare, of course, many ways to represent sequences in terms of pairs.\n\nOne\nparticularly straightforward representation is illustrated in\nfigure,\nwhere the sequence 1, 2, 3, 4 is represented as a chain of pairs.\n\nThe\nhead\nof each pair is the\ncorresponding item in the chain, and the\ntail\nof the pair is the next pair in the chain.\n\nThe\ntail\nof the final pair signals the end of the\nsequence,\nrepresented in box-and-pointer\ndiagrams as a diagonal line\nand in programs as\nJavaScripts primitive value null.\n\nThe entire sequence is constructed by nested\npair\noperations:\n\n```javascript\ncons_example\n\npair(1,\n pair(2,\n pair(3,\n pair(4, null))));\n```\n\nSuch a sequence of pairs, formed by nested\npair applications,\nis called a\nlist , and\nour JavaScript environment\nprovides a primitive called\nto help in constructing\nlists.\n\nThe above sequence could be produced by\nlist(1, 2, 3, 4).\n\nIn general,\n\n```javascript\nlist(a$_{1}$, a$_{2}$, $\\ldots$, a$_{n}$)\n```\n\nis equivalent to\n\n```javascript\npair(a$_{1}$, pair(a$_{2}$, pair($\\ldots$, pair(a$_{n}$, null)$\\ldots$)))\n```\n\nOur interpreter prints pairs using a textual representation of box-and-pointer diagrams that we call box notation.\n\nThe result of pair(1, 2) is printed as [1, 2], and the data object in figure is printed as [1, [2, [3, [4, null]]]]:\n\n```javascript\none_four\n one_four_example\n [ 2, [ 3, [ 4, null ] ] ]\n\nconst one_through_four = list(1, 2, 3, 4);\n```\n\n```javascript\none_four\n one_four_example\n\none_through_four;\n\ntail(one_through_four);\n\n[1, [2, [3, [4, null]]]]\n```\n\nWe can think of\nhead\nas selecting the first item in the list, and of\ntail\nas selecting the sublist consisting of all but the first item.\n\nNested\napplications of\nhead\nand\ntail\ncan be used to extract the second, third, and subsequent items in the\nlist.", + "token_count": 300, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -210,8 +220,8 @@ "chunk_id": "Building_Abstractions_with_Data_Representing_Sequences_1" }, { - "content": "The constructor\npair\nmakes a list like the original one, but with an additional item at the\nbeginning.\n\n```javascript\ncdr_one_four\n one_four\n [ 2, [ 3, [ 4, null ] ] ]\n\ntail(one_through_four);\n```\n\n```javascript\ncar_cdr_one_four\n one_four\n 2\n\nhead(tail(one_through_four));\n```\n\n```javascript\ncons_one_four\n one_four\n [ 2, [ 3, [ 4, null ] ] ]\n\npair(10, one_through_four);\n\ntail(tail(pair(10, one_through_four)));\n```\n\n```javascript\ncons5_one_four\n one_four\n [ 2, [ 3, [ 4, null ] ] ]\n\npair(5, one_through_four);\n\ntail(tail(pair(5, one_through_four)));\n```\n\n```javascript\nThe value null, used to terminate\n\tthe chain of pairs, can be thought of as a sequence of no elements, the\n empty list.\n```\n\nBox notation is sometimes difficult to read.\n\nIn this book, when we want to\nindicate the list nature of a data structure, we will employ the\nalternative\nlist notation : Whenever possible, list notation uses\napplications\nof\n\nwe write\n\nin list notation.\n\nThe use of pairs to represent sequences of elements as lists is accompanied\nby conventional programming techniques for manipulating lists by\nsuccessively\ntail to walk down the lists.\n\nFor example, the\nfunction\nlist_ref\ntakes as arguments a list and a number $n$ and\nreturns the $n$ th item of the list.\n\nIt is\ncustomary to number the elements of the list beginning with 0.\n\nThe method\nfor computing\nlist_ref\nis the following:\n-\n-\nFor $n=0$ ,\nlist_ref\nshould return the\nhead\nof the list.\n-\n-\nOtherwise,\nlist_ref\nshould return the $(n-1)$ st item of the\ntail\nof the list.\n\n```javascript\nlist_ref\n list_ref_example\n\nfunction list_ref(items, n) {\n return n === 0\n ? head(items)\n : list_ref(tail(items), n - 1);\n}\n```\n\n```javascript\nlist_ref_example\n list_ref\n 16\n\nconst squares = list(1, 4, 9, 16, 25);\n\nlist_ref(squares, 3);\n```\n\n```javascript\nmanual_squares\n\nconst squares = list(1, 4, 9, 16, 25);\n```\n\n```javascript\nmanual_odds\n\nconst odds = list(1, 3, 5, 7);\n```\n\nOften we\nwalk down the whole list.", - "token_count": 306, + "content": "Nested\napplications of\nhead\nand\ntail\ncan be used to extract the second, third, and subsequent items in the\nlist.\n\n```javascript\ncar_one_four\n one_four\n 1\n\nhead(one_through_four);\n\n1\n```\n\n```javascript\ncdr_one_four\n one_four\n [ 2, [ 3, [ 4, null ] ] ]\n\ntail(one_through_four);\n\n[2, [3, [4, null]]]\n```\n\n```javascript\ncar_cdr_one_four\n one_four\n 2\n\nhead(tail(one_through_four));\n\n2\n```\n\n```javascript\ncons_one_four\n one_four\n [ 2, [ 3, [ 4, null ] ] ]\n\npair(10, one_through_four);\n\ntail(tail(pair(10, one_through_four)));\n\n[10, [1, [2, [3, [4, null]]]]]\n```\n\n```javascript\ncons5_one_four\n one_four\n [ 2, [ 3, [ 4, null ] ] ]\n\npair(5, one_through_four);\n\ntail(tail(pair(5, one_through_four)));\n\n[5, [1, [2, [3, [4, null]]]]]\n```\n\nThe value null, used to terminate the chain of pairs, can be thought of as a sequence of no elements, the empty list.\n\nBox notation is sometimes difficult to read.\n\nIn this book, when we want to\nindicate the list nature of a data structure, we will employ the\nalternative\nlist notation : Whenever possible, list notation uses\napplications\nof whose evaluation would result in the\ndesired structure.\n\nFor example, instead of the box notation\n\n```javascript\n[1, [[2, 3], [[4, [5, null]], [6, null]]]]\n```\n\nwe write\n\n```javascript\nlist(1, [2, 3], list(4, 5), 6)\n```\n\nin list notation.\n\nThe use of pairs to represent sequences of elements as lists is accompanied\nby conventional programming techniques for manipulating lists by\nsuccessively\nusing tail to walk down the lists.\n\nFor example, the\nfunction\nlist_ref\ntakes as arguments a list and a number $n$ and\nreturns the $n$ th item of the list.\n\nIt is\ncustomary to number the elements of the list beginning with 0.\n\nThe method\nfor computing\nlist_ref\nis the following:\n-\n-\nFor $n=0$ ,\nlist_ref\nshould return the\nhead\nof the list.\n-\n-\nOtherwise,\nlist_ref\nshould return the $(n-1)$ st item of the\ntail\nof the list.", + "token_count": 301, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -220,8 +230,8 @@ "chunk_id": "Building_Abstractions_with_Data_Representing_Sequences_2" }, { - "content": "Often we\nwalk down the whole list.\n\nTo aid in this,\nour JavaScript environment\nincludes a primitive\npredicate\nis_null,\nwhich tests whether its argument is the empty list.\n\nThe\nfunction\n\n```javascript\nlength\n length_example\n 4\n\nfunction length(items) {\n return is_null(items)\n ? 0\n : 1 + length(tail(items));\n}\n```\n\n```javascript\nlength_example\n\nconst odds = list(1, 3, 5, 7);\n\nlength(odds);\n```\n\nThe function\nimplements a simple recursive plan.\n\nThe reduction step is:\n-\n-\nThe\ntail\nof the list.\n\nThis is applied successively until we reach the base case:\n-\n-\nThe\nWe could also compute\n\n```javascript\nlength_iter\n length_example\n 4\n\nfunction length(items) {\n function length_iter(a, count) {\n return is_null(a)\n ? count\n : length_iter(tail(a), count + 1);\n }\n return length_iter(items, 0);\n}\n```\n\nAnother conventional programming technique is to tail, as in the function\n\n```javascript\nappend_example\n append\n manual_squares\n manual_odds\n 9\n\nappend(squares, odds);\n\nlength(append(squares, odds));\n```\n\n```javascript\nappend_example2\n append\n manual_squares\n manual_odds\n 9\n\nappend(odds, squares);\n\nlength(append(odds, squares));\n```\n\nThe function append\nis also implemented using a recursive plan.\n\nTo\n-\n-\nIf\n-\n-\nOtherwise,\ntail\nof\nadjoin\nthe\nhead\nof\nto the result:\n\n```javascript\nappend\n append_example\n 9\n\nfunction append(list1, list2) {\n return is_null(list1)\n ? list2\n : pair(head(list1), append(tail(list1), list2));\n}\n```\n\nWe want to rewrite the function\n\n```javascript\nus_coins\n\nconst us_coins = list(50, 25, 10, 5, 1);\nconst uk_coins = list(100, 50, 20, 10, 5, 2, 1);\n```\n\nWe could then call\n\n```javascript\ncc_example\n cc\n us_coins\n\ncc(100, us_coins);\n```\n\nTo do this will require changing the program\n\n```javascript\ncc_helpers\n\n// first_denomination, except_first_denomination\n// and no_more to be given by student\n```\n\n```javascript\ncc\n cc_helpers\n\tcc_example\n\nfunction cc(amount, coin_values) {\n return amount === 0\n ? 1\n : amount < 0 || no_more(coin_values)\n ? 0\n : cc(amount, except_first_denomination(coin_values)) +\n cc(amount - first_denomination(coin_values), coin_values);\n}\n```\n\nDefine the\nfunctions\nfirst_denomination,\nexcept_first_denomination,\nand\nno_more\nin terms of primitive operations on list structures.\n\nDoes the order of\nthe list\ncoin_values\naffect the answer produced by", - "token_count": 315, + "content": "The method\nfor computing\nlist_ref\nis the following:\n-\n-\nFor $n=0$ ,\nlist_ref\nshould return the\nhead\nof the list.\n-\n-\nOtherwise,\nlist_ref\nshould return the $(n-1)$ st item of the\ntail\nof the list.\n\n```javascript\nlist_ref_example\n list_ref\n 16\n\nconst squares = list(1, 4, 9, 16, 25);\n\nlist_ref(squares, 3);\n\n16\n```\n\n```javascript\nmanual_squares\n\nconst squares = list(1, 4, 9, 16, 25);\n```\n\n```javascript\nmanual_odds\n\nconst odds = list(1, 3, 5, 7);\n```\n\nOften we\nwalk down the whole list.\n\nTo aid in this,\nour JavaScript environment\nincludes a primitive\npredicate\nis_null,\nwhich tests whether its argument is the empty list.\n\nThe\nfunction\n, which returns the number of items in\na list, illustrates this typical pattern of use:\n\n```javascript\nlength\n length_example\n 4\n\nfunction length(items) {\n return is_null(items)\n ? 0\n : 1 + length(tail(items));\n}\n```\n\n```javascript\nlength_example\n\nconst odds = list(1, 3, 5, 7);\n\nlength(odds);\n\n4\n```\n\nThe\nfunction\nimplements a simple recursive plan.\n\nThe reduction step is:\n-\n-\nThe of any list is 1 plus the\nof the\ntail\nof the list.\n\nThis is applied successively until we reach the base case:\n-\n-\nThe of the empty list is 0.\n\nWe could also compute in an iterative\nstyle:\n\n```javascript\nlength_iter\n length_example\n 4\n\nfunction length(items) {\n function length_iter(a, count) {\n return is_null(a)\n ? count\n : length_iter(tail(a), count + 1);\n }\n return length_iter(items, 0);\n}\n```\n\nAnother conventional programming technique is to construct an answer list by adjoining elements to the front of the list with while walking down a list\n\nusing tail, as in the function , which takes two lists as arguments and combines their elements to make a new list:\n\n```javascript\nappend_example\n append\n manual_squares\n manual_odds\n 9\n\nappend(squares, odds);\n\nlength(append(squares, odds));\n\nlist(1, 4, 9, 16, 25, 1, 3, 5, 7)\n```\n\n```javascript\nappend_example2\n append\n manual_squares\n manual_odds\n 9\n\nappend(odds, squares);\n\nlength(append(odds, squares));\n\nlist(1, 3, 5, 7, 1, 4, 9, 16, 25)\n```", + "token_count": 316, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -230,8 +240,8 @@ "chunk_id": "Building_Abstractions_with_Data_Representing_Sequences_3" }, { - "content": "Does the order of\nthe list\ncoin_values\naffect the answer produced by\n\nOne extremely useful operation is to apply some transformation to each\nelement in a list and generate the list of results.\n\nFor instance, the\nfollowing\nfunction\nscales each number in a list by a given factor:\n\n```javascript\nscale_list\n scale_list_example\n [ 30, [ 40, [ 50, null ] ] ]\n\nfunction scale_list(items, factor) {\n return is_null(items)\n ? null\n : pair(head(items) * factor,\n scale_list(tail(items), factor));\n}\n```\n\n```javascript\nscale_list_example\n\nscale_list(list(1, 2, 3, 4, 5), 10);\n\ntail(tail(scale_list(list(1, 2, 3, 4, 5), 10)));\n```\n\nWe can abstract this general idea and capture it as a common pattern\nexpressed as a higher-order\nfunction,\njust as in section.\n\nThe\nhigher-order\nfunction\nhere is called\nThe function map\ntakes as arguments a\nfunction\nof one argument and a list, and returns a list of the results produced by\napplying the\nfunction\nto each element in the list:\n\n```javascript\nmap\n map_example\n [ 2.5, [ 11.6, [ 17, null ] ] ]\n\nfunction map(fun, items) {\n return is_null(items)\n ? null\n : pair(fun(head(items)),\n map(fun, tail(items)));\n}\n```\n\n```javascript\nmap_example\n abs_definition\n map\n [ 2.5, [ 11.6, [ 17, null ] ] ]\n\nmap(abs, list(-10, 2.5, -11.6, 17));\n\ntail(map(abs, list(-10, 2.5, -11.6, 17)));\n```\n\n```javascript\nmap_example2\n map\n [ 4, [ 9, [ 16, null ] ] ]\n\nmap(x => x * x, list(1, 2, 3, 4));\n\ntail(map(x => x * x, list(1, 2, 3, 4)));\n```\n\nNow we can give a new definition of scale_list in terms of\n\n```javascript\nscale_list2\n scale_list_example\n [ 30, [ 40, [ 50, null ] ] ]\n\nfunction scale_list(items, factor) {\n return map(x => x * factor, items);\n}\n```\n\nThe function map\nis an important construct, not only because it captures a common pattern,\nbut because it establishes a higher level of abstraction in dealing with\nlists.", - "token_count": 302, + "content": "using tail, as in the function , which takes two lists as arguments and combines their elements to make a new list:\n\nTo\nlists\nand ,\ndo the following:\n-\n-\nIf is the empty list, then the\nresult is just.\n-\n-\nOtherwise, the\ntail\nof and\n, and\nadjoin\nthe\nhead\nof\nto the result:\n\n```javascript\nappend\n append_example\n 9\n\nfunction append(list1, list2) {\n return is_null(list1)\n ? list2\n : pair(head(list1), append(tail(list1), list2));\n}\n```\n\nWe want to rewrite the\nfunction\nso that its second argument is a list of\nthe values of the coins to use rather than an integer specifying which\ncoins to use.\n\nWe could then have lists that defined each kind of\ncurrency:\n\n```javascript\nus_coins\n\nconst us_coins = list(50, 25, 10, 5, 1);\nconst uk_coins = list(100, 50, 20, 10, 5, 2, 1);\n```\n\nWe could then call as follows:\n\n```javascript\ncc_example\n cc\n us_coins\n\ncc(100, us_coins);\n\n292\n```\n\nTo do this will require changing the program\nsomewhat.\n\nIt will still have the same\nform, but it will access its second argument differently, as follows:\n\n```javascript\ncc_helpers\n\n// first_denomination, except_first_denomination\n// and no_more to be given by student\n```\n\n```javascript\ncc\n cc_helpers\n\tcc_example\n\nfunction cc(amount, coin_values) {\n return amount === 0\n ? 1\n : amount < 0 || no_more(coin_values)\n ? 0\n : cc(amount, except_first_denomination(coin_values)) +\n cc(amount - first_denomination(coin_values), coin_values);\n}\n```\n\nDefine the\nfunctions\nfirst_denomination,\nexcept_first_denomination,\nand\nno_more\nin terms of primitive operations on list structures.\n\nDoes the order of\nthe list\ncoin_values\naffect the answer produced by ?\n\nWhy or why not?\n\nOne extremely useful operation is to apply some transformation to each\nelement in a list and generate the list of results.\n\nFor instance, the\nfollowing\nfunction\nscales each number in a list by a given factor:", + "token_count": 292, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -240,8 +250,8 @@ "chunk_id": "Building_Abstractions_with_Data_Representing_Sequences_4" }, { - "content": "The function map\nis an important construct, not only because it captures a common pattern,\nbut because it establishes a higher level of abstraction in dealing with\nlists.\n\nIn the original definition of\nscale_list,\nthe recursive structure of the program draws attention to the\nelement-by-element processing of the list.\n\nDefining\nscale_list\nin terms of t) but that we\nthink about the process differently.\n\nIn effect,\nfunctions\nthat transform lists from the details of how the elements of the list are\nextracted and combined.\n\nLike the barriers shown in\nfigure,\nthis abstraction gives us the flexibility to change the low-level details\nof how sequences are implemented, while preserving the conceptual framework\nof operations that transform sequences to sequences.\n\nSection expands\non this use of sequences as a framework for organizing programs.\n\nLouis then tries to fix his bug by interchanging the arguments to pair:\n\n```javascript\nsquare_list3\n square_definition\n square_list_warning\n square_list_example\n 16\n\nfunction square_list(items) {\n function iter(things, answer) {\n return is_null(things)\n ? answer\n : iter(tail(things),\n pair(answer,\n square(head(things))));\n }\n return iter(items, null);\n}\n```\n\nThis doesn t work either.\n\nExplain.", - "token_count": 176, + "content": "For instance, the\nfollowing\nfunction\nscales each number in a list by a given factor:\n\n```javascript\nscale_list_example\n\nscale_list(list(1, 2, 3, 4, 5), 10);\n\n[10, [20, [30, [40, [50, null]]]]]\n\ntail(tail(scale_list(list(1, 2, 3, 4, 5), 10)));\n```\n\nWe can abstract this general idea and capture it as a common pattern\nexpressed as a higher-order\nfunction,\njust as in section.\n\nThe\nhigher-order\nfunction\nhere is called.\n\nThe function map\ntakes as arguments a\nfunction\nof one argument and a list, and returns a list of the results produced by\napplying the\nfunction\nto each element in the list:\n\n```javascript\nmap\n map_example\n [ 2.5, [ 11.6, [ 17, null ] ] ]\n\nfunction map(fun, items) {\n return is_null(items)\n ? null\n : pair(fun(head(items)),\n map(fun, tail(items)));\n}\n```\n\n```javascript\nmap_example\n abs_definition\n map\n [ 2.5, [ 11.6, [ 17, null ] ] ]\n\nmap(abs, list(-10, 2.5, -11.6, 17));\n\ntail(map(abs, list(-10, 2.5, -11.6, 17)));\n\n[10, [2.5, [11.6, [17, null]]]]\n```\n\n```javascript\nmap_example2\n map\n [ 4, [ 9, [ 16, null ] ] ]\n\nmap(x => x * x, list(1, 2, 3, 4));\n\ntail(map(x => x * x, list(1, 2, 3, 4)));\n\n[1, [4, [9, [16, null]]]]\n```\n\nNow we can give a new definition of scale_list in terms of :\n\n```javascript\nscale_list2\n scale_list_example\n [ 30, [ 40, [ 50, null ] ] ]\n\nfunction scale_list(items, factor) {\n return map(x => x * factor, items);\n}\n```\n\nThe function map\nis an important construct, not only because it captures a common pattern,\nbut because it establishes a higher level of abstraction in dealing with\nlists.\n\nIn the original definition of\nscale_list,\nthe recursive structure of the program draws attention to the\nelement-by-element processing of the list.\n\nDefining\nscale_list\nin terms of suppresses that level of\ndetail and emphasizes that scaling transforms a list of elements to a list\nof results.", + "token_count": 302, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -249,6 +259,16 @@ "chunk_index": 5, "chunk_id": "Building_Abstractions_with_Data_Representing_Sequences_5" }, + { + "content": "Defining\nscale_list\nin terms of suppresses that level of\ndetail and emphasizes that scaling transforms a list of elements to a list\nof results.\n\nIn effect,\nhelps establish an abstraction barrier\nthat isolates the implementation of\nfunctions\nthat transform lists from the details of how the elements of the list are\nextracted and combined.\n\nLike the barriers shown in\nfigure,\nthis abstraction gives us the flexibility to change the low-level details\nof how sequences are implemented, while preserving the conceptual framework\nof operations that transform sequences to sequences.\n\nSection expands\non this use of sequences as a framework for organizing programs.\n\nLouis then tries to fix his bug by interchanging the arguments to pair:\n\n```javascript\nsquare_list3\n square_definition\n square_list_warning\n square_list_example\n 16\n\nfunction square_list(items) {\n function iter(things, answer) {\n return is_null(things)\n ? answer\n : iter(tail(things),\n pair(answer,\n square(head(things))));\n }\n return iter(items, null);\n}\n```\n\nThis doesn t work either.\n\nExplain.", + "token_count": 147, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Hierarchical Data and the Closure Property", + "subsection": "Representing Sequences", + "chunk_index": 6, + "chunk_id": "Building_Abstractions_with_Data_Representing_Sequences_6" + }, { "content": "In working with compound data, we ve stressed how data abstraction\npermits us to design programs without becoming enmeshed in the details\nof data representations, and how abstraction preserves for us the\nflexibility to experiment with alternative representations.\n\nIn this\nsection, we introduce another powerful design principle for working\nwith data structures the use of conventional interfaces.\n\nIn section we saw how\nprogram abstractions, implemented as higher-order\nfunctions,\ncan capture common patterns in programs that deal with numerical data.\n\nOur\nability to formulate analogous operations for working with compound data\ndepends crucially on the style in which we manipulate our data structures.\n\nConsider, for example, the following\nfunction,\nanalogous to the\ncount_leaves\nfunction\nof section , which takes a tree as argument\nand computes the sum of the squares of the leaves that are odd:\n\n```javascript\nodd_definition\n\nfunction is_odd(n) {\n return n % 2 === 1;\n}\n```\n\n```javascript\nsquare_definition\n odd_definition\n sum_odd_squares_example\n 34\n\nfunction sum_odd_squares(tree) {\n return is_null(tree)\n ? 0\n : ! is_pair(tree)\n ? is_odd(tree) ? square(tree) : 0\n : sum_odd_squares(head(tree)) +\n sum_odd_squares(tail(tree));\n}\n```\n\n```javascript\nsum_odd_squares_example\n\nsum_odd_squares(list(list(2, 3), list(4, 5)));\n```\n\nOn the surface, this function is very different from the following one, which constructs a list of all the even Fibonacci numbers ${\\textrm{Fib}}(k)$ , where\n\n$k$ is less than or equal to a given integer $n$ :\n\n```javascript\neven_definition\n fib_definition\n even_fibs_example\n [ 2, [ 8, [ 34, null ] ] ]\n\nfunction even_fibs(n) {\n function next(k) {\n if (k > n) {\n return null;\n } else {\n const f = fib(k);\n return is_even(f)\n ? pair(f, next(k + 1))\n : next(k + 1);\n }\n }\n return next(0);\n}\n```\n\n```javascript\neven_fibs_example\n\neven_fibs(9);\n\ntail(even_fibs(9));\n```\n\nDespite the fact that these two\nfunctions\nare structurally very different, a more abstract description of the two\ncomputations reveals a great deal of similarity.", "token_count": 300, @@ -260,8 +280,8 @@ "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_1" }, { - "content": "Despite the fact that these two\nfunctions\nare structurally very different, a more abstract description of the two\ncomputations reveals a great deal of similarity.\n\nThe first program\n-\n-\nenumerates the leaves of a tree;\n-\n-\nfilters them, selecting the odd ones;\n-\n-\nsquares each of the selected ones; and\n-\n-\naccumulates the results using\n+,\nstarting with 0.\n\nThe second program\n-\n-\nenumerates the integers from 0 to $n$ ;\n-\n-\ncomputes the Fibonacci number for each integer;\n-\n-\nfilters them, selecting the even ones; and\n-\n-\naccumulates the results using\npair,\nstarting with the empty list.\n\nA signal-processing engineer would find it natural to conceptualize these\nprocesses in terms of\nfigure.\n\nIn\nsum_odd_squares,\nwe begin with an\nenumerator , which generates a signal consisting of\nthe leaves of a given tree.\n\nThis signal is passed through a\nfilter , which eliminates all but the odd elements.\n\nThe resulting\nsignal is in turn passed through a\nmap , which is a transducer that applies the\nfunction\nto each element.\n\nThe output of the map is then fed to an\naccumulator , which combines the elements using\n+,\nstarting from an initial 0.\n\nThe plan for\neven_fibs\nis analogous.\n\nUnfortunately, the two\nfunction declarations\nabove fail to exhibit this signal-flow structure.\n\nFor instance, if we\nexamine the\nsum_odd_squares\nfunction,\nwe find that the enumeration is implemented partly by the\nis_null\nand\nis_pair\ntests and partly by the tree-recursive structure of the\nfunction.\n\nSimilarly, the accumulation is found partly in the tests and partly in the\naddition used in the recursion.\n\nIn general, there are no distinct parts of\neither\nfunction\nthat correspond to the elements in the signal-flow description.", - "token_count": 287, + "content": "Despite the fact that these two\nfunctions\nare structurally very different, a more abstract description of the two\ncomputations reveals a great deal of similarity.\n\nThe second program\n-\n-\nenumerates the integers from 0 to $n$ ;\n-\n-\ncomputes the Fibonacci number for each integer;\n-\n-\nfilters them, selecting the even ones; and\n-\n-\naccumulates the results using\npair,\nstarting with the empty list.\n\nA signal-processing engineer would find it natural to conceptualize these\nprocesses in terms of\nsignals flowing through a cascade of stages, each of\nwhich implements part of the program plan, as shown in\nfigure.\n\nIn\nsum_odd_squares,\nwe begin with an\nenumerator , which generates a signal consisting of\nthe leaves of a given tree.\n\nThis signal is passed through a\nfilter , which eliminates all but the odd elements.\n\nThe resulting\nsignal is in turn passed through a\nmap , which is a transducer that applies the\nfunction\nto each element.\n\nThe output of the map is then fed to an\naccumulator , which combines the elements using\n+,\nstarting from an initial 0.\n\nThe plan for\neven_fibs\nis analogous.\n\nUnfortunately, the two\nfunction declarations\nabove fail to exhibit this signal-flow structure.\n\nFor instance, if we\nexamine the\nsum_odd_squares\nfunction,\nwe find that the enumeration is implemented partly by the\nis_null\nand\nis_pair\ntests and partly by the tree-recursive structure of the\nfunction.\n\nSimilarly, the accumulation is found partly in the tests and partly in the\naddition used in the recursion.\n\nIn general, there are no distinct parts of\neither\nfunction\nthat correspond to the elements in the signal-flow description.\n\nOur two\nfunctions\ndecompose the computations in a different way, spreading the enumeration\nover the program and mingling it with the map, the filter, and the\naccumulation.", + "token_count": 295, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -270,8 +290,8 @@ "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_2" }, { - "content": "In general, there are no distinct parts of\neither\nfunction\nthat correspond to the elements in the signal-flow description.\n\nOur two\nfunctions\ndecompose the computations in a different way, spreading the enumeration\nover the program and mingling it with the map, the filter, and the\naccumulation.\n\nIf we could organize our programs to make the signal-flow\nstructure manifest in the\nfunctions\nwe write, this would increase the conceptual clarity of the resulting\nprogram.\n\nThe key to organizing programs so as to more clearly reflect the\nsignal-flow structure is to concentrate on the signals that\nflow from one stage in the process to the next.\n\nIf we represent these\nsignals as lists, then we can use list operations to implement the\nprocessing at each of the stages.\n\nFor instance, we can implement the\nmapping stages of the signal-flow diagrams using the\nfunction\nfrom section :\n\n```javascript\nsquare_definition\n map\n 25\n\nmap(square, list(1, 2, 3, 4, 5));\n\nlist_ref(map(square, list(1, 2, 3, 4, 5)), 4);\n```\n\nFiltering a sequence to select only those elements that satisfy a given predicate is accomplished by\n\n```javascript\nfilter\n filter_odd\n [ 1, [ 3, [ 5, null ] ] ]\n\nfunction filter(predicate, sequence) {\n return is_null(sequence)\n ? null\n : predicate(head(sequence))\n ? pair(head(sequence),\n filter(predicate, tail(sequence)))\n : filter(predicate, tail(sequence));\n}\n```\n\n```javascript\nfilter_odd\n [ 1, [ 3, [ 5, null ] ] ]\n\nfunction is_odd(n) {\n return n % 2 === 1;\n}\nfilter(is_odd, list(1, 2, 3, 4, 5));\n```\n\nFor example,\n\n```javascript\nfilter\n odd_definition\n\nfilter(is_odd, list(1, 2, 3, 4, 5));\n```\n\nAccumulations can be implemented by\n\n```javascript\naccumulate\n\nfunction accumulate(op, initial, sequence) {\n return is_null(sequence)\n ? initial\n : op(head(sequence),\n accumulate(op, initial, tail(sequence)));\n}\n```\n\n```javascript\nsimple_plus\n\nfunction plus(x, y) {\n return x + y;\n}\n```\n\n```javascript\naccumulate\n simple_plus\n 15\n\naccumulate(plus, 0, list(1, 2, 3, 4, 5));\n```\n\n```javascript\nsimple_times\n\nfunction times(x, y) {\n return x * y;\n}\n```", - "token_count": 310, + "content": "Our two\nfunctions\ndecompose the computations in a different way, spreading the enumeration\nover the program and mingling it with the map, the filter, and the\naccumulation.\n\nThe key to organizing programs so as to more clearly reflect the\nsignal-flow structure is to concentrate on the signals that\nflow from one stage in the process to the next.\n\nIf we represent these\nsignals as lists, then we can use list operations to implement the\nprocessing at each of the stages.\n\nFor instance, we can implement the\nmapping stages of the signal-flow diagrams using the\nfunction\nfrom section :\n\n```javascript\nsquare_definition\n map\n 25\n\nmap(square, list(1, 2, 3, 4, 5));\n\nlist_ref(map(square, list(1, 2, 3, 4, 5)), 4);\n\nlist(1, 4, 9, 16, 25)\n```\n\nFiltering a sequence to select only those elements that satisfy a given predicate is accomplished by\n\n```javascript\nfilter\n filter_odd\n [ 1, [ 3, [ 5, null ] ] ]\n\nfunction filter(predicate, sequence) {\n return is_null(sequence)\n ? null\n : predicate(head(sequence))\n ? pair(head(sequence),\n filter(predicate, tail(sequence)))\n : filter(predicate, tail(sequence));\n}\n```\n\n```javascript\nfilter_odd\n [ 1, [ 3, [ 5, null ] ] ]\n\nfunction is_odd(n) {\n return n % 2 === 1;\n}\nfilter(is_odd, list(1, 2, 3, 4, 5));\n```\n\nFor example,\n\n```javascript\nfilter\n odd_definition\n\nfilter(is_odd, list(1, 2, 3, 4, 5));\n\nlist(1, 3, 5)\n```\n\nAccumulations can be implemented by\n\n```javascript\naccumulate\n\nfunction accumulate(op, initial, sequence) {\n return is_null(sequence)\n ? initial\n : op(head(sequence),\n accumulate(op, initial, tail(sequence)));\n}\n```\n\n```javascript\nsimple_plus\n\nfunction plus(x, y) {\n return x + y;\n}\n```\n\n```javascript\naccumulate\n simple_plus\n 15\n\naccumulate(plus, 0, list(1, 2, 3, 4, 5));\n\n15\n```\n\n```javascript\nsimple_times\n\nfunction times(x, y) {\n return x * y;\n}\n```\n\n```javascript\naccumulate\n simple_times\n 120\n\naccumulate(times, 1, list(1, 2, 3, 4, 5));\n\n120\n```\n\n```javascript\naccumulate\n [ 3, [ 4, [ 5, null ] ] ]\n\naccumulate(pair, null, list(1, 2, 3, 4, 5));\n\ntail(tail(accumulate(pair, null, list(1, 2, 3, 4, 5))));\n\nlist(1, 2, 3, 4, 5)\n```", + "token_count": 318, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -280,8 +300,8 @@ "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_3" }, { - "content": "Accumulations can be implemented by\n\n```javascript\naccumulate\n simple_times\n 120\n\naccumulate(times, 1, list(1, 2, 3, 4, 5));\n```\n\n```javascript\naccumulate\n [ 3, [ 4, [ 5, null ] ] ]\n\naccumulate(pair, null, list(1, 2, 3, 4, 5));\n\ntail(tail(accumulate(pair, null, list(1, 2, 3, 4, 5))));\n```\n\nAll that remains to implement signal-flow diagrams is to enumerate the\nsequence of elements to be processed.\n\nFor\neven_fibs,\nwe need to generate the sequence of integers in a given range, which we\ncan do as follows:\n\n```javascript\nenumerate_interval\n enumerate_interval_example\n [ 5, [ 6, [ 7, null ] ] ]\n\nfunction enumerate_interval(low, high) {\n return low > high\n ? null\n : pair(low,\n enumerate_interval(low + 1, high));\n}\n```\n\n```javascript\nenumerate_interval_example\n enumerate_interval\n\nenumerate_interval(2, 7);\n\ntail(tail(tail(enumerate_interval(2, 7))));\n```\n\nTo enumerate the leaves of a tree, we can use\n\n```javascript\nenumerate_tree\n enumerate_tree_example\n [ 3, [ 4, [ 5, null ] ] ]\n\nfunction enumerate_tree(tree) {\n return is_null(tree)\n ? null\n : ! is_pair(tree)\n ? list(tree)\n : append(enumerate_tree(head(tree)),\n enumerate_tree(tail(tree)));\n}\n```\n\n```javascript\nenumerate_tree_example\n enumerate_tree\n\nenumerate_tree(list(1, list(2, list(3, 4)), 5));\n\ntail(tail(enumerate_tree(list(1, list(2, list(3, 4)), 5))));\n```\n\nNow we can reformulate\nsum_odd_squares\nand\neven_fibs\nas in the signal-flow diagrams.\n\nFor\nsum_odd_squares,\nwe enumerate the sequence of leaves of the tree, filter this to keep only\nthe odd numbers in the sequence, square each element, and sum the results:\n\n```javascript\nsquare_definition\n simple_plus\n odd_definition\n enumerate_tree\n sum_odd_squares_example\n 34\n\nfunction sum_odd_squares(tree) {\n return accumulate(plus,\n 0,\n map(square,\n filter(is_odd,\n enumerate_tree(tree))));\n}\n```\n\nFor even_fibs, we enumerate the integers from 0 to $n$ , generate the Fibonacci number for each of these integers, filter the resulting sequence to\n\nkeep only the even elements, and accumulate the results into a list:\n\n```javascript\neven_definition\n fib_definition\n enumerate_interval\n even_fibs_example\n [ 2, [ 8, [ 34, null ] ] ]\n\nfunction even_fibs(n) {\n return accumulate(pair,\n null,\n filter(is_even,\n map(fib,\n enumerate_interval(0, n))));\n}\n```", - "token_count": 297, + "content": "Accumulations can be implemented by\n\nFor\neven_fibs,\nwe need to generate the sequence of integers in a given range, which we\ncan do as follows:\n\n```javascript\nenumerate_interval\n enumerate_interval_example\n [ 5, [ 6, [ 7, null ] ] ]\n\nfunction enumerate_interval(low, high) {\n return low > high\n ? null\n : pair(low,\n enumerate_interval(low + 1, high));\n}\n```\n\n```javascript\nenumerate_interval_example\n enumerate_interval\n\nenumerate_interval(2, 7);\n\ntail(tail(tail(enumerate_interval(2, 7))));\n\nlist(2, 3, 4, 5, 6, 7)\n```\n\nTo enumerate the leaves of a tree, we can use\n\n```javascript\nenumerate_tree\n enumerate_tree_example\n [ 3, [ 4, [ 5, null ] ] ]\n\nfunction enumerate_tree(tree) {\n return is_null(tree)\n ? null\n : ! is_pair(tree)\n ? list(tree)\n : append(enumerate_tree(head(tree)),\n enumerate_tree(tail(tree)));\n}\n```\n\n```javascript\nenumerate_tree_example\n enumerate_tree\n\nenumerate_tree(list(1, list(2, list(3, 4)), 5));\n\ntail(tail(enumerate_tree(list(1, list(2, list(3, 4)), 5))));\n\nlist(1, 2, 3, 4, 5)\n```\n\nNow we can reformulate\nsum_odd_squares\nand\neven_fibs\nas in the signal-flow diagrams.\n\nFor\nsum_odd_squares,\nwe enumerate the sequence of leaves of the tree, filter this to keep only\nthe odd numbers in the sequence, square each element, and sum the results:\n\n```javascript\nsquare_definition\n simple_plus\n odd_definition\n enumerate_tree\n sum_odd_squares_example\n 34\n\nfunction sum_odd_squares(tree) {\n return accumulate(plus,\n 0,\n map(square,\n filter(is_odd,\n enumerate_tree(tree))));\n}\n```\n\nFor even_fibs, we enumerate the integers from 0 to $n$ , generate the Fibonacci number for each of these integers, filter the resulting sequence to\n\nkeep only the even elements, and accumulate the results into a list:\n\n```javascript\neven_definition\n fib_definition\n enumerate_interval\n even_fibs_example\n [ 2, [ 8, [ 34, null ] ] ]\n\nfunction even_fibs(n) {\n return accumulate(pair,\n null,\n filter(is_even,\n map(fib,\n enumerate_interval(0, n))));\n}\n```\n\nThe value of expressing programs as sequence operations is that this\nhelps us make program designs that are modular, that is, designs that\nare constructed by combining relatively independent pieces.\n\nWe can\nencourage modular design by providing a library of standard components\ntogether with a conventional interface for connecting the components\nin flexible ways.", + "token_count": 306, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -290,8 +310,8 @@ "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_4" }, { - "content": "keep only the even elements, and accumulate the results into a list:\n\nThe value of expressing programs as sequence operations is that this\nhelps us make program designs that are modular, that is, designs that\nare constructed by combining relatively independent pieces.\n\nWe can\nencourage modular design by providing a library of standard components\ntogether with a conventional interface for connecting the components\nin flexible ways.\n\nModular construction sum_odd_squares and even_fibs functions in a program that constructs a list of the squares of the first $n+1$ Fibonacci numbers:\n\n```javascript\nlist_fib_squares\n square_definition\n fib_definition\n enumerate_interval\n list_fib_squares_example\n 11\n\nfunction list_fib_squares(n) {\n return accumulate(pair,\n null,\n map(square,\n map(fib,\n enumerate_interval(0, n))));\n}\n```\n\n```javascript\nlist_fib_squares_example\n list_fib_squares\n\nlist_fib_squares(10);\n\nlength(list_fib_squares(10));\n```\n\nWe can rearrange the pieces and use them in computing the product of the squares of the odd integers in a sequence:\n\n```javascript\nproduct_of_squares_of_odd_elements\n square_definition\n odd_definition\n simple_times\n product_of_squares_of_odd_elements_example\n 225\n\nfunction product_of_squares_of_odd_elements(sequence) {\n return accumulate(times,\n 1,\n map(square,\n filter(is_odd, sequence)));\n}\n```\n\n```javascript\nproduct_of_squares_of_odd_elements_example\n product_of_squares_of_odd_elements\n\nproduct_of_squares_of_odd_elements(list(1, 2, 3, 4, 5));\n```\n\nWe can also formulate conventional data-processing applications in terms of\nsequence operations.\n\nSuppose we have a sequence of personnel records and\nwe want to find the salary of the highest-paid programmer.\n\nAssume that we\nhave a selector\nis_programmer\nthat tests if a record is for a programmer.\n\nThen we can write\n\n```javascript\nlinus\n\nconst my_records = list(list(\"Linus\", \"programmer\", 30000),\n list(\"Richard\", \"programmer\", 25000),\n list(\"Bill\", \"manager\", 2500000));\nfunction is_programmer(record) {\n return head(tail(record)) === \"programmer\";\n}\nfunction salary(record) {\n return head(tail(tail(record)));\n}\nsalary_of_highest_paid_programmer(my_records);\n```\n\n```javascript\nlinus\n 30000\n\nfunction salary_of_highest_paid_programmer(records) {\n return accumulate(math_max,\n 0,\n map(salary,\n filter(is_programmer, records)));\n}\n```\n\nThese examples give just a hint of the vast range of operations that can be expressed as sequence operations.\n\nSequences, implemented here as lists, serve as a conventional interface\nthat permits us to combine processing modules.", - "token_count": 293, + "content": "We can\nencourage modular design by providing a library of standard components\ntogether with a conventional interface for connecting the components\nin flexible ways.\n\nIn real signal-processing applications, for example,\ndesigners regularly build systems by cascading elements selected from\nstandardized families of filters and transducers.\n\nSimilarly, sequence\noperations provide a library of standard program elements that we can mix\nand match.\n\nFor instance, we can reuse pieces from the\nsum_odd_squares\nand\neven_fibs\nfunctions\nin a program that constructs a list of the squares of the first\n$n+1$ Fibonacci numbers:\n\n```javascript\nlist_fib_squares\n square_definition\n fib_definition\n enumerate_interval\n list_fib_squares_example\n 11\n\nfunction list_fib_squares(n) {\n return accumulate(pair,\n null,\n map(square,\n map(fib,\n enumerate_interval(0, n))));\n}\n```\n\n```javascript\nlist_fib_squares_example\n list_fib_squares\n\nlist_fib_squares(10);\n\nlength(list_fib_squares(10));\n\nlist(0, 1, 1, 4, 9, 25, 64, 169, 441, 1156, 3025)\n```\n\nWe can rearrange the pieces and use them in computing the product of the squares of the odd integers in a sequence:\n\n```javascript\nproduct_of_squares_of_odd_elements\n square_definition\n odd_definition\n simple_times\n product_of_squares_of_odd_elements_example\n 225\n\nfunction product_of_squares_of_odd_elements(sequence) {\n return accumulate(times,\n 1,\n map(square,\n filter(is_odd, sequence)));\n}\n```\n\n```javascript\nproduct_of_squares_of_odd_elements_example\n product_of_squares_of_odd_elements\n\nproduct_of_squares_of_odd_elements(list(1, 2, 3, 4, 5));\n\n225\n```\n\nWe can also formulate conventional data-processing applications in terms of\nsequence operations.\n\nSuppose we have a sequence of personnel records and\nwe want to find the salary of the highest-paid programmer.\n\nAssume that we\nhave a selector that returns the salary\nof a record, and a predicate\nis_programmer\nthat tests if a record is for a programmer.\n\nThen we can write\n\n```javascript\nlinus\n\nconst my_records = list(list(\"Linus\", \"programmer\", 30000),\n list(\"Richard\", \"programmer\", 25000),\n list(\"Bill\", \"manager\", 2500000));\nfunction is_programmer(record) {\n return head(tail(record)) === \"programmer\";\n}\nfunction salary(record) {\n return head(tail(tail(record)));\n}\nsalary_of_highest_paid_programmer(my_records);\n```\n\n```javascript\nlinus\n 30000\n\nfunction salary_of_highest_paid_programmer(records) {\n return accumulate(math_max,\n 0,\n map(salary,\n filter(is_programmer, records)));\n}\n```\n\nThese examples give just a hint of the vast range of operations that can be expressed as sequence operations.", + "token_count": 299, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -300,8 +320,8 @@ "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_5" }, { - "content": "Sequences, implemented here as lists, serve as a conventional interface\nthat permits us to combine processing modules.\n\nAdditionally, when we\nuniformly represent structures as sequences, we have localized the\ndata-structure dependencies in our programs to a small number of sequence\noperations.\n\nBy changing these, we can experiment with alternative\nrepresentations of sequences, while leaving the overall design of our\nprograms intact.\n\nWe will exploit this capability in\nsection , when we generalize the\nsequence-processing paradigm to admit infinite sequences.\n\nWe can extend the sequence paradigm to include many computations that are\ncommonly expressed using nested loops. $n$ , find all ordered pairs of distinct positive\nintegers $i$ and $j$ ,\nwhere $1\\leq j < i\\leq n$ , such that\n$i +j$ is prime.\n\nFor example, if\n$n$ is 6, then the pairs are the following:\n\\[\n\\begin{array}{c|ccccccc}\ni & 2 & 3 & 4 & 4 & 5 & 6 & 6 \\\\\nj & 1 & 2 & 1 & 3 & 2 & 1 & 5 \\\\\n\\hline\ni+j & 3 & 5 & 5 & 7 & 7 & 7 & 11\n\\end{array}\n\\]\nA natural way to organize this computation is to generate the sequence\nof all ordered pairs of positive integers less than or equal to\n$n$ , filter to select those pairs whose sum is\nprime, and then, for each pair $(i, j)$ that\npasses through the filter, produce the triple\n$(i, j, i+j)$.\n\nHere is a way to generate the sequence of pairs: For each integer\n$i\\leq n$ , enumerate the integers\n$j < i$ , and for each such\n$i$ and $j$\ngenerate the pair $(i, j)$.\n\nIn terms of\nsequence operations, we map along the sequence\nenumerate_interval(1, n).", - "token_count": 287, + "content": "These examples give just a hint of the vast range of operations that can be expressed as sequence operations.\n\nAdditionally, when we\nuniformly represent structures as sequences, we have localized the\ndata-structure dependencies in our programs to a small number of sequence\noperations.\n\nBy changing these, we can experiment with alternative\nrepresentations of sequences, while leaving the overall design of our\nprograms intact.\n\nWe will exploit this capability in\nsection , when we generalize the\nsequence-processing paradigm to admit infinite sequences.\n\nWe can extend the sequence paradigm to include many computations that are\ncommonly expressed using nested loops.\n\nConsider this problem: Given a positive integer\n$n$ , find all ordered pairs of distinct positive\nintegers $i$ and $j$ ,\nwhere $1\\leq j < i\\leq n$ , such that\n$i +j$ is prime.\n\nFor example, if\n$n$ is 6, then the pairs are the following:\n\\[\n\\begin{array}{c|ccccccc}\ni & 2 & 3 & 4 & 4 & 5 & 6 & 6 \\\\\nj & 1 & 2 & 1 & 3 & 2 & 1 & 5 \\\\\n\\hline\ni+j & 3 & 5 & 5 & 7 & 7 & 7 & 11\n\\end{array}\n\\]\nA natural way to organize this computation is to generate the sequence\nof all ordered pairs of positive integers less than or equal to\n$n$ , filter to select those pairs whose sum is\nprime, and then, for each pair $(i, j)$ that\npasses through the filter, produce the triple\n$(i, j, i+j)$.\n\nHere is a way to generate the sequence of pairs: For each integer\n$i\\leq n$ , enumerate the integers\n$j < i$ , and for each such\n$i$ and $j$\ngenerate the pair $(i, j)$.\n\nIn terms of\nsequence operations, we map along the sequence\nenumerate_interval(1, n).", + "token_count": 296, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -310,8 +330,8 @@ "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_6" }, { - "content": "In terms of\nsequence operations, we map along the sequence\nenumerate_interval(1, n).\n\nFor each $i$ in this sequence, we map along the\nsequence\nenumerate_interval(1, i - 1).\n\nFor each $j$ in this latter sequence, we\ngenerate the pair\nlist(i, j).\n\nThis gives us a sequence of pairs for each $i$.\n\nCombining all the sequences for all the $i$ (by\naccumulating with\n\n```javascript\nenumerate_interval_n\n\nconst n = 6;\n```\n\n```javascript\nenumerate_interval\n enumerate_interval_n\n 15\n\naccumulate(append,\n null,\n map(i => map(j => list(i, j),\n enumerate_interval(1, i - 1)),\n enumerate_interval(1, n)));\n\nlength(accumulate(append,\n null,\n map(i => map(j => list(i, j),\n enumerate_interval(1, i - 1)),\n enumerate_interval(1, n))));\n```\n\nThe combination of mapping and accumulating with function:\n\n```javascript\nflatmap\n flatmap_example\n 8\n\nfunction flatmap(f, seq) {\n return accumulate(append, null, map(f, seq));\n}\n```\n\n```javascript\nflatmap_example\n\nflatmap(x => list(x, x), list(1, 2, 3, 4));\n\nlength(flatmap(x => list(x, x), list(1, 2, 3, 4)));\n```\n\nNow filter this sequence of pairs to find those whose sum is prime.\n\nThe\nfilter predicate is called for each element of the sequence; its argument\nis a pair and it must extract the integers from the pair.\n\nThus, the\npredicate to apply to each element in the sequence is\n\n```javascript\nprime_sum\n prime_definition\n prime_sum_example\n true\n\nfunction is_prime_sum(pair) {\n return is_prime(head(pair) + head(tail(pair)));\n}\n```\n\n```javascript\nprime_sum_example\n\nis_prime_sum(list(8, 9));\n```\n\nFinally, generate the sequence of results by mapping over the filtered pairs using the following function, which constructs a triple consisting of the two elements\n\nof the pair along with their sum:\n\n```javascript\nmake_pair_sum\n make_pair_sum_example\n [ 8, [ 9, [ 17, null ] ] ]\n\nfunction make_pair_sum(pair) {\n return list(head(pair), head(tail(pair)),\n head(pair) + head(tail(pair)));\n}\n```\n\n```javascript\nmake_pair_sum_example\n\nmake_pair_sum(list(8, 9));\n```\n\nCombining all these steps yields the complete function:\n\n```javascript\nprime_sum_pairs\n make_pair_sum\n prime_sum\n flatmap\n enumerate_interval\n prime_sum_pairs_example\n 7\n\nfunction prime_sum_pairs(n) {\n return map(make_pair_sum,\n filter(is_prime_sum,\n flatmap(i => map(j => list(i, j),\n enumerate_interval(1, i - 1)),\n enumerate_interval(1, n))));\n}\n```\n\n```javascript\nprime_sum_pairs_example\n\nprime_sum_pairs(6);\n\nlength(prime_sum_pairs(6));\n```", - "token_count": 314, + "content": "In terms of\nsequence operations, we map along the sequence\nenumerate_interval(1, n).\n\nFor each $j$ in this latter sequence, we\ngenerate the pair\nlist(i, j).\n\nThis gives us a sequence of pairs for each $i$.\n\nCombining all the sequences for all the $i$ (by\naccumulating with ) produces the\nrequired sequence of pairs:\n\n```javascript\nenumerate_interval_n\n\nconst n = 6;\n```\n\n```javascript\nenumerate_interval\n enumerate_interval_n\n 15\n\naccumulate(append,\n null,\n map(i => map(j => list(i, j),\n enumerate_interval(1, i - 1)),\n enumerate_interval(1, n)));\n\nlength(accumulate(append,\n null,\n map(i => map(j => list(i, j),\n enumerate_interval(1, i - 1)),\n enumerate_interval(1, n))));\n```\n\nThe combination of mapping and accumulating with is so common in this sort of program that we will isolate it as a separate function:\n\n```javascript\nflatmap\n flatmap_example\n 8\n\nfunction flatmap(f, seq) {\n return accumulate(append, null, map(f, seq));\n}\n```\n\n```javascript\nflatmap_example\n\nflatmap(x => list(x, x), list(1, 2, 3, 4));\n\nlength(flatmap(x => list(x, x), list(1, 2, 3, 4)));\n```\n\nNow filter this sequence of pairs to find those whose sum is prime.\n\nThe\nfilter predicate is called for each element of the sequence; its argument\nis a pair and it must extract the integers from the pair.\n\nThus, the\npredicate to apply to each element in the sequence is\n\n```javascript\nprime_sum\n prime_definition\n prime_sum_example\n true\n\nfunction is_prime_sum(pair) {\n return is_prime(head(pair) + head(tail(pair)));\n}\n```\n\n```javascript\nprime_sum_example\n\nis_prime_sum(list(8, 9));\n```\n\nFinally, generate the sequence of results by mapping over the filtered pairs using the following function, which constructs a triple consisting of the two elements\n\nof the pair along with their sum:\n\n```javascript\nmake_pair_sum\n make_pair_sum_example\n [ 8, [ 9, [ 17, null ] ] ]\n\nfunction make_pair_sum(pair) {\n return list(head(pair), head(tail(pair)),\n head(pair) + head(tail(pair)));\n}\n```\n\n```javascript\nmake_pair_sum_example\n\nmake_pair_sum(list(8, 9));\n```\n\nCombining all these steps yields the complete function:\n\n```javascript\nprime_sum_pairs\n make_pair_sum\n prime_sum\n flatmap\n enumerate_interval\n prime_sum_pairs_example\n 7\n\nfunction prime_sum_pairs(n) {\n return map(make_pair_sum,\n filter(is_prime_sum,\n flatmap(i => map(j => list(i, j),\n enumerate_interval(1, i - 1)),\n enumerate_interval(1, n))));\n}\n```", + "token_count": 317, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -320,8 +340,8 @@ "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_7" }, { - "content": "Combining all these steps yields the complete function:\n\nNested mappings are also useful for sequences other than those that\nenumerate intervals.\n\nSuppose we wish to generate all the\n$S$ ; that is, all the ways of ordering\nthe items in the set.\n\nFor instance, the permutations of\n$\\{1, 2, 3\\}$ are\n$\\{1, 2, 3\\}$ ,\n$\\{ 1, 3, 2\\}$ ,\n$\\{2, 1, 3\\}$ ,\n$\\{ 2, 3, 1\\}$ ,\n$\\{ 3, 1, 2\\}$ , and\n$\\{ 3, 2, 1\\}$.\n\nHere is a plan for generating\nthe permutations of $S$ : For each item\n$x$ in $S$ ,\nrecursively generate the sequence of permutations of\n$S-x$ , $x$ to the front of each one.\n\nThis yields, for\neach $x$ in $S$ , the\nsequence of permutations of $S$ that begin\nwith $x$.\n\nCombining these sequences for\nall $x$ gives all the permutations\nof $S$ :\n\n```javascript\nflatmap\n permutations_example\n 6\n\nfunction permutations(s) {\n return is_null(s) // empty set?\n ? list(null) // sequence containing empty set\n : flatmap(x => map(p => pair(x, p),\n permutations(remove(x, s))),\n s);\n}\n```\n\n```javascript\npermutations_example\n\npermutations(list(1, 2, 3));\n\nlength(permutations(list(1, 2, 3)));\n```\n\nNotice how this strategy reduces the problem of generating permutations of\n$S$ to the problem of generating the\npermutations of sets with fewer elements than\n$S$.\n\nIn the terminal case, we work our way down\nto the empty list, which represents a set of no elements.\n\nFor this, we\ngenerate\nlist(null),\nwhich is a sequence with one item, namely the set with no elements.\n\nThe\nremove\nfunction\nused in\n\n```javascript\nremove\n remove_example\n 4\n\nfunction remove(item, sequence) {\n return filter(x => ! (x === item),\n sequence);\n}\n```\n\n```javascript\nremove_example\n\nlength(remove(3, list(1, 2, 3, 4, 5)));\n\nlength(remove(3, list(1, 2, 3, 4, 5)));\n```", - "token_count": 286, + "content": "Combining all these steps yields the complete function:\n\nNested mappings are also useful for sequences other than those that\nenumerate intervals.\n\nSuppose we wish to generate all the\npermutations\nof a set $S$ ; that is, all the ways of ordering\nthe items in the set.\n\nFor instance, the permutations of\n$\\{1, 2, 3\\}$ are\n$\\{1, 2, 3\\}$ ,\n$\\{ 1, 3, 2\\}$ ,\n$\\{2, 1, 3\\}$ ,\n$\\{ 2, 3, 1\\}$ ,\n$\\{ 3, 1, 2\\}$ , and\n$\\{ 3, 2, 1\\}$.\n\nHere is a plan for generating\nthe permutations of $S$ : For each item\n$x$ in $S$ ,\nrecursively generate the sequence of permutations of\n$S-x$ , and adjoin\n$x$ to the front of each one.\n\nThis yields, for\neach $x$ in $S$ , the\nsequence of permutations of $S$ that begin\nwith $x$.\n\nCombining these sequences for\nall $x$ gives all the permutations\nof $S$ :\n\n```javascript\nflatmap\n permutations_example\n 6\n\nfunction permutations(s) {\n return is_null(s) // empty set?\n ? list(null) // sequence containing empty set\n : flatmap(x => map(p => pair(x, p),\n permutations(remove(x, s))),\n s);\n}\n```\n\n```javascript\npermutations_example\n\npermutations(list(1, 2, 3));\n\nlength(permutations(list(1, 2, 3)));\n```\n\nNotice how this strategy reduces the problem of generating permutations of\n$S$ to the problem of generating the\npermutations of sets with fewer elements than\n$S$.\n\nIn the terminal case, we work our way down\nto the empty list, which represents a set of no elements.\n\nFor this, we\ngenerate\nlist(null),\nwhich is a sequence with one item, namely the set with no elements.\n\nThe\nremove\nfunction\nused in returns all the items in\na given sequence except for a given item.\n\nThis can be expressed as a\nsimple filter:\n\n```javascript\nremove\n remove_example\n 4\n\nfunction remove(item, sequence) {\n return filter(x => ! (x === item),\n sequence);\n}\n```", + "token_count": 298, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -330,8 +350,8 @@ "chunk_id": "Building_Abstractions_with_Data_Sequences_as_Conventional_Interfaces_8" }, { - "content": "The\nremove\nfunction\nused in\n\nWe implement this solution as a\nfunction\nqueens,\nwhich returns a sequence of all solutions to the problem of placing\n$n$ queens on an\n$n\\times n$ chessboard.\n\nThe function queens\nhas an internal\nfunction\nqueens_cols\nthat returns the sequence of all ways to place queens in the first\n$k$ columns of the board.\n\n```javascript\nexample_queens\n\nqueens(8);\n\nlength(queens(8));\n```\n\n```javascript\nqueens\n flatmap\n enumerate_interval\n example_queens\n\nfunction queens(board_size) {\n function queen_cols(k) {\n return k === 0\n ? list(empty_board)\n : filter(positions => is_safe(k, positions),\n flatmap(rest_of_queens =>\n map(new_row =>\n adjoin_position(new_row, k,\n rest_of_queens),\n enumerate_interval(1, board_size)),\n queen_cols(k - 1)));\n }\n return queen_cols(board_size);\n}\n```\n\nIn this\nfunction\nrest_of_queens\nis a way to place $k-1$ queens in the first\n$k-1$ columns, and\nnew_row\nis a proposed row in which to place the queen for the\n$k$ th column.\n\nComplete the program by\nimplementing the representation for sets of board positions, including the\nfunction\nadjoin_position,\nwhich adjoins a new row-column position to a set of positions, and\nempty_board,\nwhich represents an empty set of positions.\n\nYou must also write the\nfunction\nis_safe,\nwhich determines for a set of positions whether the queen in the\n$k$ th column is safe with respect to the others.\n\n(Note that we need only check whether the new queen is safe the\nother queens are already guaranteed safe with respect to each other.)", - "token_count": 224, + "content": "This can be expressed as a\nsimple filter:\n\nWe implement this solution as a\nfunction\nqueens,\nwhich returns a sequence of all solutions to the problem of placing\n$n$ queens on an\n$n\\times n$ chessboard.\n\nThe function queens\nhas an internal\nfunction\nqueens_cols\nthat returns the sequence of all ways to place queens in the first\n$k$ columns of the board.\n\n```javascript\nexample_queens\n\nqueens(8);\n\nlength(queens(8));\n```\n\n```javascript\nqueens\n flatmap\n enumerate_interval\n example_queens\n\nfunction queens(board_size) {\n function queen_cols(k) {\n return k === 0\n ? list(empty_board)\n : filter(positions => is_safe(k, positions),\n flatmap(rest_of_queens =>\n map(new_row =>\n adjoin_position(new_row, k,\n rest_of_queens),\n enumerate_interval(1, board_size)),\n queen_cols(k - 1)));\n }\n return queen_cols(board_size);\n}\n```\n\nIn this\nfunction\nrest_of_queens\nis a way to place $k-1$ queens in the first\n$k-1$ columns, and\nnew_row\nis a proposed row in which to place the queen for the\n$k$ th column.\n\nComplete the program by\nimplementing the representation for sets of board positions, including the\nfunction\nadjoin_position,\nwhich adjoins a new row-column position to a set of positions, and\nempty_board,\nwhich represents an empty set of positions.\n\nYou must also write the\nfunction\nis_safe,\nwhich determines for a set of positions whether the queen in the\n$k$ th column is safe with respect to the others.\n\n(Note that we need only check whether the new queen is safe the\nother queens are already guaranteed safe with respect to each other.)", + "token_count": 227, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Hierarchical Data and the Closure Property", @@ -350,9 +370,9 @@ "chunk_id": "Building_Abstractions_with_Data_Introduction_to_Data_Abstraction_1" }, { - "content": "make_@rat and selectors\n\nWe can envision the structure of the rational-number system as\nshown in\nfigure.\n\nThe horizontal lines represent abstraction barriers that isolate\ndifferent levels of the system.\n\nAt each level, the barrier\nseparates the programs (above) that use the data abstraction from the\nprograms (below) that implement the data abstraction.\n\nPrograms that\nuse rational numbers manipulate them solely in terms of the\nfunctions\nsupplied for public use by the rational-number package:\nadd_rat,\nsub_rat,\nmul_rat,\ndiv_rat,\nand\nequal_rat.\n\nThese, in turn, are implemented solely in terms of the\nmake_rat,\npair,\nhead,\nand\ntail.\n\nIn effect,\nfunctions\nat each level are the interfaces that define the abstraction barriers and\nconnect the different levels.\n\nThis simple idea has many advantages.\n\nOne advantage is that it makes\nprograms much easier to maintain and to modify.\n\nAny complex data\nstructure can be represented in a variety of ways with the primitive\ndata structures provided by a programming language.\n\nOf course, the\nchoice of representation influences the programs that operate on it;\nthus, if the representation were to be changed at some later time, all\nsuch programs might have to be modified accordingly.\n\nThis task could\nbe time-consuming and expensive in the case of large programs unless\nthe dependence on the representation were to be confined by design to\na very few program modules.\n\nFor example, an alternate way to address the problem of functions:\n\n```javascript\nmake_rat_4\n [ 1, 2 ]\n gcd_definition\n print_rat_example5\n\nfunction make_rat(n, d) {\n return pair(n, d);\n}\nfunction numer(x) {\n const g = gcd(head(x), tail(x));\n return head(x) / g;\n}\nfunction denom(x) {\n const g = gcd(head(x), tail(x));\n return tail(x) / g;\n}\n```", - "token_count": 274, - "has_code": true, + "content": "Before continuing with more examples of compound data and data\nabstraction, let us consider some of the issues raised by the\nrational-number example.\n\nWe defined the rational-number operations in\nterms of a constructor\nmake_@rat\nand selectors and.\n\nIn general, the underlying idea of data\nabstraction is to identify for each type of data object a basic set of\noperations in terms of which all manipulations of data objects of that type\nwill be expressed, and then to use only those operations in manipulating the\ndata.\n\nWe can envision the structure of the rational-number system as\nshown in\nfigure.\n\nThe horizontal lines represent abstraction barriers that isolate\ndifferent levels of the system.\n\nAt each level, the barrier\nseparates the programs (above) that use the data abstraction from the\nprograms (below) that implement the data abstraction.\n\nPrograms that\nuse rational numbers manipulate them solely in terms of the\nfunctions\nsupplied for public use by the rational-number package:\nadd_rat,\nsub_rat,\nmul_rat,\ndiv_rat,\nand\nequal_rat.\n\nThese, in turn, are implemented solely in terms of the\nconstructor and\nselectors\nmake_rat,\n, and ,\nwhich themselves are implemented in terms of pairs.\n\nThe details of how\npairs are implemented are irrelevant to the rest of the rational-number\npackage so long as pairs can be manipulated by the use of\npair,\nhead,\nand\ntail.\n\nIn effect,\nfunctions\nat each level are the interfaces that define the abstraction barriers and\nconnect the different levels.\n\nThis simple idea has many advantages.\n\nOne advantage is that it makes\nprograms much easier to maintain and to modify.\n\nAny complex data\nstructure can be represented in a variety of ways with the primitive\ndata structures provided by a programming language.", + "token_count": 278, + "has_code": false, "chapter": "Building Abstractions with Data", "section": "Introduction to Data Abstraction", "subsection": "Abstraction Barriers", @@ -360,8 +380,8 @@ "chunk_id": "Building_Abstractions_with_Data_Abstraction_Barriers_1" }, { - "content": "For example, an alternate way to address the problem of functions:\n\n```javascript\nprint_rat_example5\n\n// printing the rational in one line requires some string\n// manipulation: stringify turns a number into a string\n// and the operator + can be applied to strings for\n// string concatenation\nfunction print_rat(x) {\n return display(stringify(numer(x)) + \"/\" + stringify(denom(x)));\n}\n\nconst one_half = make_rat(1, 2);\n\nprint_rat(one_half);\n\nconst one_half = make_rat(1, 2);\n\none_half;\n```\n\nThe difference between this implementation and the previous one lies in when we compute the functions add_rat, sub_rat, and so on do not have to\n\nbe modified at all.\n\nConstraining the dependence on the representation to a few interface\nfunctions\nhelps us design programs as well as modify them, because it allows us to\nmaintain the flexibility to consider alternate implementations.\n\nTo continue\nwith our simple example, suppose we are designing a rational-number package\nand we can t decide initially whether to perform the", - "token_count": 153, + "content": "Any complex data\nstructure can be represented in a variety of ways with the primitive\ndata structures provided by a programming language.\n\nThis task could\nbe time-consuming and expensive in the case of large programs unless\nthe dependence on the representation were to be confined by design to\na very few program modules.\n\nFor example, an alternate way to address the problem of\nreducing rational\nnumbers to lowest terms is to perform the reduction whenever we\naccess the parts of a rational number, rather than when we construct\nit.\n\nThis leads to different constructor and selector\nfunctions:\n\n```javascript\nmake_rat_4\n [ 1, 2 ]\n gcd_definition\n print_rat_example5\n\nfunction make_rat(n, d) {\n return pair(n, d);\n}\nfunction numer(x) {\n const g = gcd(head(x), tail(x));\n return head(x) / g;\n}\nfunction denom(x) {\n const g = gcd(head(x), tail(x));\n return tail(x) / g;\n}\n```\n\n```javascript\nprint_rat_example5\n\n// printing the rational in one line requires some string\n// manipulation: stringify turns a number into a string\n// and the operator + can be applied to strings for\n// string concatenation\nfunction print_rat(x) {\n return display(stringify(numer(x)) + \"/\" + stringify(denom(x)));\n}\n\nconst one_half = make_rat(1, 2);\n\nprint_rat(one_half);\n\nconst one_half = make_rat(1, 2);\n\none_half;\n```\n\nThe difference between this implementation and the previous one lies in when\nwe compute the.\n\nIf in our typical use of\nrational numbers we access the numerators and denominators of the same\nrational numbers many times, it would be preferable to compute the\nwhen the rational numbers are constructed.\n\nIf not, we may be better off waiting until access time to compute the.\n\nIn any case, when we change from one\nrepresentation to the other, the\nfunctions\nadd_rat,\nsub_rat,\nand so on do not have to be modified at all.", + "token_count": 288, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Introduction to Data Abstraction", @@ -370,8 +390,18 @@ "chunk_id": "Building_Abstractions_with_Data_Abstraction_Barriers_2" }, { - "content": "Suppose we want to do\n\nLet us begin by assuming that we already have a way of constructing a\nrational number from a numerator and a denominator.\n\nWe also assume\nthat, given a rational number, we have a way of extracting (or\nselecting) its numerator and its denominator.\n\nLet us further assume\nthat the constructor and selectors are available as\nfunctions:\n-\n-\nmake_rat($n$, $d$)\n$n$ and whose denominator is the integer\n$d$.\n-\n-\nnumer($x$)\n$x$.\n-\n-\ndenom($x$)\n$x$.\n\nWe are using here a powerful strategy of synthesis:\nwishful thinking.\n\nWe haven t yet said how a rational number\nis represented, or how the\nfunctions\nmake_rat\nshould be implemented.\n\nEven so, if we did have these three\nfunctions,\nwe could then add, subtract, multiply, divide, and test equality by using\nthe following relations:\n\\[\n\\begin{array}{rll}\n\\dfrac{n_{1}}{d_{1}}+\\dfrac{n_{2}}{d_{2}}\n&=&\\dfrac{n_{1}d_{2}+n_{2}d_{1}}{d_{1}d_{2}}\\\\[15pt]\n\\dfrac{n_{1}}{d_{1}}-\\dfrac{n_{2}}{d_{2}}\n&=&\\dfrac{n_{1}d_{2}-n_{2}d_{1}}{d_{1}d_{2}}\\\\[15pt]\n\\dfrac{n_{1}}{d_{1}}\\cdot\\dfrac{n_{2}}{d_{2}}\n&=&\\dfrac{n_{1}n_{2}}{d_{1}d_{2}}\\\\[15pt]\n\\dfrac{n_{1}/d_{1}}{n_{2}/d_{2}}\n&=&\\dfrac{n_{1}d_{2}}{d_{1}n_{2}}\\\\[15pt]\n\\dfrac{n_{1}}{d_{1}}\n&=&\\dfrac{n_{2}}{d_{2}}\\ \\quad \\textrm{if and only if}\\ \\ \\ n_{1}d_{2}\\ =\\ n_{2}d_{1}\n\\end{array}\n\\]\n\nWe can express these rules as functions:\n\n```javascript\nadd_rat\n make_rat2\n\nfunction add_rat(x, y) {\n return make_rat(numer(x) * denom(y) + numer(y) * denom(x),\n denom(x) * denom(y));\n}\nfunction sub_rat(x, y) {\n return make_rat(numer(x) * denom(y) - numer(y) * denom(x),\n denom(x) * denom(y));\n}\nfunction mul_rat(x, y) {\n return make_rat(numer(x) * numer(y),\n denom(x) * denom(y));\n}\nfunction div_rat(x, y) {\n return make_rat(numer(x) * denom(y),\n denom(x) * numer(y));\n}\nfunction equal_rat(x, y) {\n return numer(x) * denom(y) === numer(y) * denom(x);\n}\n```\n\nNow we have the operations on rational numbers defined in terms of the\nselector and constructor\nfunctions\nmake_rat.\n\nBut we haven t yet defined these.\n\nWhat we need is some way to glue\ntogether a numerator and a denominator to form a rational number.", - "token_count": 283, + "content": "In any case, when we change from one\nrepresentation to the other, the\nfunctions\nadd_rat,\nsub_rat,\nand so on do not have to be modified at all.\n\nTo continue\nwith our simple example, suppose we are designing a rational-number package\nand we can t decide initially whether to perform the\nat construction time or at selection time.\n\nThe data-abstraction methodology gives us a way to defer that decision\nwithout losing the ability to make progress on the rest of the system.", + "token_count": 81, + "has_code": false, + "chapter": "Building Abstractions with Data", + "section": "Introduction to Data Abstraction", + "subsection": "Abstraction Barriers", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Data_Abstraction_Barriers_3" + }, + { + "content": "Suppose we want to do\narithmetic with rational numbers.\n\nWe want to be\nable to add, subtract, multiply, and divide them and to test whether\ntwo rational numbers are equal.\n\nLet us begin by assuming that we already have a way of constructing a\nrational number from a numerator and a denominator.\n\nWe also assume\nthat, given a rational number, we have a way of extracting (or\nselecting) its numerator and its denominator.\n\nLet us further assume\nthat the constructor and selectors are available as\nfunctions:\n-\n-\nmake_rat($n$, $d$)\nreturns the\nrational number whose numerator is the integer\n$n$ and whose denominator is the integer\n$d$.\n-\n-\nnumer($x$)\nreturns the numerator of the rational number\n$x$.\n-\n-\ndenom($x$)\nreturns the denominator of the rational number\n$x$.\n\nWe are using here a powerful strategy of synthesis:\nwishful thinking.\n\nWe haven t yet said how a rational number\nis represented, or how the\nfunctions\n, , and\nmake_rat\nshould be implemented.\n\nEven so, if we did have these three\nfunctions,\nwe could then add, subtract, multiply, divide, and test equality by using\nthe following relations:\n\\[\n\\begin{array}{rll}\n\\dfrac{n_{1}}{d_{1}}+\\dfrac{n_{2}}{d_{2}}\n&=&\\dfrac{n_{1}d_{2}+n_{2}d_{1}}{d_{1}d_{2}}\\\\[15pt]\n\\dfrac{n_{1}}{d_{1}}-\\dfrac{n_{2}}{d_{2}}\n&=&\\dfrac{n_{1}d_{2}-n_{2}d_{1}}{d_{1}d_{2}}\\\\[15pt]\n\\dfrac{n_{1}}{d_{1}}\\cdot\\dfrac{n_{2}}{d_{2}}\n&=&\\dfrac{n_{1}n_{2}}{d_{1}d_{2}}\\\\[15pt]\n\\dfrac{n_{1}/d_{1}}{n_{2}/d_{2}}\n&=&\\dfrac{n_{1}d_{2}}{d_{1}n_{2}}\\\\[15pt]\n\\dfrac{n_{1}}{d_{1}}\n&=&\\dfrac{n_{2}}{d_{2}}\\ \\quad \\textrm{if and only if}\\ \\ \\ n_{1}d_{2}\\ =\\ n_{2}d_{1}\n\\end{array}\n\\]\n\nWe can express these rules as functions:\n\n```javascript\nadd_rat\n make_rat2\n\nfunction add_rat(x, y) {\n return make_rat(numer(x) * denom(y) + numer(y) * denom(x),\n denom(x) * denom(y));\n}\nfunction sub_rat(x, y) {\n return make_rat(numer(x) * denom(y) - numer(y) * denom(x),\n denom(x) * denom(y));\n}\nfunction mul_rat(x, y) {\n return make_rat(numer(x) * numer(y),\n denom(x) * denom(y));\n}\nfunction div_rat(x, y) {\n return make_rat(numer(x) * denom(y),\n denom(x) * numer(y));\n}\nfunction equal_rat(x, y) {\n return numer(x) * denom(y) === numer(y) * denom(x);\n}\n```", + "token_count": 290, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Introduction to Data Abstraction", @@ -380,8 +410,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Arithmetic_Operations_for_Rational_Numbers_1" }, { - "content": "What we need is some way to glue\ntogether a numerator and a denominator to form a rational number.\n\nTo enable us to implement the concrete level of our data abstraction, our\nJavaScript environment\nprovides a compound structure called a\npair , which can be constructed with the\nprimitive function\npair.\n\nThis\nfunction\ntakes two arguments and returns a compound data object that contains the\ntwo arguments as parts.\n\nGiven a pair, we can extract the parts using the\nprimitive\nfunctions\nhead\nand\ntail.\n\nThus, we can use\npair,\nhead,\nand\ntail\nas follows:\n\n```javascript\ncons_1_2\n cons_1_2_example\n\nconst x = pair(1, 2);\n```\n\n```javascript\ncons_1_2_example\n\t1\n cons_1_2\n\nhead(x);\n```\n\n```javascript\ncons_1_2_example2\n\t2\n cons_1_2\n\ntail(x);\n```\n\nNotice that a pair is a data object that can be given a name and\nmanipulated, just like a primitive data object.\n\nMoreover,\npair\ncan be used to form pairs whose elements are pairs, and so on:\n\n```javascript\ncons_1_2_3_4\n cons_1_2_3_4_example\n\nconst x = pair(1, 2);\n\nconst y = pair(3, 4);\n\nconst z = pair(x, y);\n```\n\n```javascript\ncons_1_2_3_4_example\n\t1\n cons_1_2_3_4\n\nhead(head(z));\n```\n\n```javascript\ncons_1_2_3_4_example2\n\t3\n cons_1_2_3_4\n\nhead(tail(z));\n```\n\nIn section we will see how this\nability to combine pairs means that pairs can be used as general-purpose\nbuilding blocks to create all sorts of complex data structures.\n\nThe single\ncompound-data primitive pair , implemented by the\nfunctions\npair,\nhead,\nand\ntail,\nis the only glue we need.\n\nData objects constructed from pairs are called\nlist-structured data.\n\nPairs offer a natural way to complete the make_rat,\n\n```javascript\nmake_rat2\n 2\n rat_example_1\n\nfunction make_rat(n, d) { return pair(n, d); }\n\nfunction numer(x) { return head(x); }\n\nfunction denom(x) { return tail(x); }\n```\n\n```javascript\nrat_example_1\n\nnumer(make_rat(2, 3));\n```\n\nAlso, in order to display the results of our computations, we can", - "token_count": 292, + "content": "We can express these rules as functions:\n\nBut we haven t yet defined these.\n\nWhat we need is some way to glue\ntogether a numerator and a denominator to form a rational number.\n\nTo enable us to implement the concrete level of our data abstraction, our\nJavaScript environment\nprovides a compound structure called a\npair , which can be constructed with the\nprimitive function\npair.\n\nThis\nfunction\ntakes two arguments and returns a compound data object that contains the\ntwo arguments as parts.\n\nGiven a pair, we can extract the parts using the\nprimitive\nfunctions\nhead\nand\ntail.\n\nThus, we can use\npair,\nhead,\nand\ntail\nas follows:\n\n```javascript\ncons_1_2\n cons_1_2_example\n\nconst x = pair(1, 2);\n```\n\n```javascript\ncons_1_2_example\n\t1\n cons_1_2\n\nhead(x);\n\n1\n```\n\n```javascript\ncons_1_2_example2\n\t2\n cons_1_2\n\ntail(x);\n\n2\n```\n\nNotice that a pair is a data object that can be given a name and\nmanipulated, just like a primitive data object.\n\nMoreover,\npair\ncan be used to form pairs whose elements are pairs, and so on:\n\n```javascript\ncons_1_2_3_4\n cons_1_2_3_4_example\n\nconst x = pair(1, 2);\n\nconst y = pair(3, 4);\n\nconst z = pair(x, y);\n```\n\n```javascript\ncons_1_2_3_4_example\n\t1\n cons_1_2_3_4\n\nhead(head(z));\n\n1\n```\n\n```javascript\ncons_1_2_3_4_example2\n\t3\n cons_1_2_3_4\n\nhead(tail(z));\n\n3\n```\n\nIn section we will see how this\nability to combine pairs means that pairs can be used as general-purpose\nbuilding blocks to create all sorts of complex data structures.\n\nThe single\ncompound-data primitive pair , implemented by the\nfunctions\npair,\nhead,\nand\ntail,\nis the only glue we need.\n\nData objects constructed from pairs are called\nlist-structured data.\n\nPairs offer a natural way to complete the\nrational-number system.\n\nSimply represent a rational number as a pair of two integers: a numerator\nand a denominator.\n\nThen\nmake_rat,\n, and\nare readily implemented as follows:", + "token_count": 294, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Introduction to Data Abstraction", @@ -390,8 +420,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Arithmetic_Operations_for_Rational_Numbers_2" }, { - "content": "Also, in order to display the results of our computations, we can\n\n```javascript\ndenominator.\n\tWe use the primitive function\n\tstringify to turn any value (here\n\ta number) into a string. The operator\n\t+ in JavaScript is\n\toverloaded; it can be applied to two numbers or to two strings,\n\tand in the latter case it returns the result of concatenating\n\tthe two strings.\n```\n\n```javascript\nprint_rat\n make_rat2\n print_rat_example_0\n\nfunction print_rat(x) {\n return display(stringify(numer(x)) + \" / \" + stringify(denom(x)));\n}\n```\n\n```javascript\nprint_rat_example_0\n\nconst one_half = make_rat(1, 2);\n\nprint_rat(one_half);\n```\n\nNow we can try our rational-number functions:\n\n```javascript\nprint_rat_example\n [ 1, 2 ]\n make_rat2\n print_rat\n\nconst one_half = make_rat(1, 2);\n\nprint_rat(one_half);\n\nconst one_half = make_rat(1, 2);\n\none_half;\n```\n\n```javascript\none_half\n\nconst one_half = make_rat(1, 2);\n```\n\n```javascript\none_third\n\nconst one_third = make_rat(1, 3);\n```\n\n```javascript\nprint_rat_example2\n [ 5, 6 ]\n add_rat\n one_half\n one_third\n print_rat\n\nprint_rat(add_rat(one_half, one_third));\n\nadd_rat(one_half, one_third);\n```\n\n```javascript\nprint_rat_example3\n [ 1, 6 ]\n add_rat\n one_half\n one_third\n print_rat\n\nprint_rat(mul_rat(one_half, one_third));\n\nmul_rat(one_half, one_third);\n```\n\n```javascript\nprint_rat_example4\n [ 6, 9 ]\n add_rat\n one_third\n print_rat\n\nprint_rat(add_rat(one_third, one_third));\n\nadd_rat(one_third, one_third);\n```\n\nAs the final example shows, our rational-number implementation does not\nmake_rat.\n\nIf we have a\nfunction\nlike the one in section that produces\n\n```javascript\nnumer\n\nfunction numer(x) {\n return head(x);\n}\nfunction denom(x) {\n return tail(x);\n}\n```\n\n```javascript\nmake_rat_3\n [ 2, 3 ]\n numer\n make_rat_3_example_1\n gcd_definition\n\nfunction make_rat(n, d) {\n const g = gcd(n, d);\n return pair(n / g, d / g);\n}\n```\n\n```javascript\nmake_rat_3_example_1\n\nmake_rat(4, 6);\n```\n\nNow we have\n\n```javascript\nnumer_rat\n add_rat_2\n one_third\n print_rat\n\nprint_rat(add_rat(one_third, one_third));\n```", - "token_count": 258, + "content": "Then\nmake_rat,\n, and\nare readily implemented as follows:\n\n```javascript\nrat_example_1\n\nnumer(make_rat(2, 3));\n```\n\nAlso, in order to display the results of our computations, we can\nprint rational numbers by printing the numerator, a slash, and the\ndenominator.\n\nWe use the primitive function stringify to turn any value (here a number) into a string.\n\nThe operator + in JavaScript is overloaded; it can be applied to two numbers or to two strings, and in the latter case it returns the result of concatenating the two strings.\n\n```javascript\nprint_rat\n make_rat2\n print_rat_example_0\n\nfunction print_rat(x) {\n return display(stringify(numer(x)) + \" / \" + stringify(denom(x)));\n}\n```\n\n```javascript\nprint_rat_example_0\n\nconst one_half = make_rat(1, 2);\n\nprint_rat(one_half);\n\n\"1 / 2\"\n```\n\nNow we can try our rational-number functions:\n\n```javascript\nprint_rat_example\n [ 1, 2 ]\n make_rat2\n print_rat\n\nconst one_half = make_rat(1, 2);\n\nprint_rat(one_half);\n\nconst one_half = make_rat(1, 2);\n\none_half;\n\n\"1 / 2\"\n```\n\n```javascript\none_half\n\nconst one_half = make_rat(1, 2);\n```\n\n```javascript\none_third\n\nconst one_third = make_rat(1, 3);\n```\n\n```javascript\nprint_rat_example2\n [ 5, 6 ]\n add_rat\n one_half\n one_third\n print_rat\n\nprint_rat(add_rat(one_half, one_third));\n\nadd_rat(one_half, one_third);\n\n\"5 / 6\"\n```\n\n```javascript\nprint_rat_example3\n [ 1, 6 ]\n add_rat\n one_half\n one_third\n print_rat\n\nprint_rat(mul_rat(one_half, one_third));\n\nmul_rat(one_half, one_third);\n\n\"1 / 6\"\n```\n\n```javascript\nprint_rat_example4\n [ 6, 9 ]\n add_rat\n one_third\n print_rat\n\nprint_rat(add_rat(one_third, one_third));\n\nadd_rat(one_third, one_third);\n\n\"6 / 9\"\n```\n\nAs the final example shows, our rational-number implementation does not\nreduce rational numbers to lowest terms.\n\nWe can remedy this by changing\nmake_rat.\n\nIf we have a\nfunction\nlike the one in section that produces\nthe greatest common divisor of two integers, we can use\nto reduce the numerator and the\ndenominator to lowest terms before constructing the pair:\n\n```javascript\nnumer\n\nfunction numer(x) {\n return head(x);\n}\nfunction denom(x) {\n return tail(x);\n}\n```\n\n```javascript\nmake_rat_3\n [ 2, 3 ]\n numer\n make_rat_3_example_1\n gcd_definition\n\nfunction make_rat(n, d) {\n const g = gcd(n, d);\n return pair(n / g, d / g);\n}\n```\n\n```javascript\nmake_rat_3_example_1\n\nmake_rat(4, 6);\n```\n\nNow we have\n\n```javascript\nnumer_rat\n add_rat_2\n one_third\n print_rat\n\nprint_rat(add_rat(one_third, one_third));\n\n\"2 / 3\"\n```", + "token_count": 335, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Introduction to Data Abstraction", @@ -400,9 +430,9 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Arithmetic_Operations_for_Rational_Numbers_3" }, { - "content": "Now we have\n\n```javascript\nadd_rat_2\n make_rat_3\n\nfunction add_rat(x, y) {\n return make_rat(numer(x) * denom(y) + numer(y) * denom(x),\n denom(x) * denom(y));\n}\nfunction sub_rat(x, y) {\n return make_rat(numer(x) * denom(y) - numer(y) * denom(x),\n denom(x) * denom(y));\n}\nfunction mul_rat(x, y) {\n return make_rat(numer(x) * numer(y),\n denom(x) * denom(y));\n}\nfunction div_rat(x, y) {\n return make_rat(numer(x) * denom(y),\n denom(x) * numer(y));\n}\nfunction equal_rat(x, y) {\n return numer(x) * denom(y) === numer(y) * denom(x);\n}\n```\n\nas desired.\n\nThis modification was accomplished by changing the constructor\nmake_@rat\nwithout changing any of the\nfunctions\n(such as\nadd_rat\nand\nmul_rat)\nthat implement the actual operations.", - "token_count": 103, - "has_code": true, + "content": "Now we have\n\nas desired.\n\nThis modification was accomplished by changing the constructor\nmake_@rat\nwithout changing any of the\nfunctions\n(such as\nadd_rat\nand\nmul_rat)\nthat implement the actual operations.", + "token_count": 30, + "has_code": false, "chapter": "Building Abstractions with Data", "section": "Introduction to Data Abstraction", "subsection": "Example: Arithmetic Operations for Rational Numbers", @@ -410,8 +440,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Arithmetic_Operations_for_Rational_Numbers_4" }, { - "content": "Alyssa P.\n\nHacker is designing a system to help people solve\nengineering problems.\n\nOne feature she wants to provide in her system\nis the ability to manipulate inexact quantities (such as measured\nparameters of physical devices) with known precision, so that when\ncomputations are done with such approximate quantities the results\nwill be numbers of known precision.\n\nElectrical engineers will be using Alyssa s system to compute\nelectrical quantities.\n\nIt is sometimes necessary for them to compute\nthe value of a parallel equivalent resistance\n$R_{p}$ of two resistors\n$R_{1}$ and $R_{2}$\n\\[\n\\begin{array}{lll}\nR_{p} & = & \\dfrac{1}{1/R_{1}+1/R_{2}}\n\\end{array}\n\\]\nResistance values are usually known only up to some\n6.8 ohms with 10% tolerance you can\nonly be sure that the resistor has a resistance between\n$6.8-0.68=6.12$ and\n$6.8+0.68=7.48$ ohms.\n\nThus, if you have a\n6.8-ohm 10% resistor in parallel with a 4.7-ohm\n5% resistor, the resistance of the combination can range from about\n2.58 ohms (if the two resistors are at the lower bounds) to about 2.97 ohms\n(if the two resistors are at the upper bounds).\n\nAlyssa s idea is to implement interval arithmetic as a\nset of arithmetic operations for combining intervals (objects\nthat represent the range of possible values of an inexact quantity).\n\nThe\nresult of adding, subtracting, multiplying, or dividing two intervals is\nitself an interval, representing the range of the result.\n\nAlyssa postulates the existence of an abstract object called an\ninterval that has two endpoints: a lower bound and an upper bound.\n\nShe also presumes that, given the endpoints of an interval, she can\nconstruct the interval using the data constructor\nmake_interval.\n\nAlyssa first writes a\nfunction\nfor adding two intervals.", - "token_count": 278, + "content": "Alyssa P.\n\nHacker is designing a system to help people solve\nengineering problems.\n\nOne feature she wants to provide in her system\nis the ability to manipulate inexact quantities (such as measured\nparameters of physical devices) with known precision, so that when\ncomputations are done with such approximate quantities the results\nwill be numbers of known precision.\n\nElectrical engineers will be using Alyssa s system to compute\nelectrical quantities.\n\nIt is sometimes necessary for them to compute\nthe value of a parallel equivalent resistance\n$R_{p}$ of two resistors\n$R_{1}$ and $R_{2}$\nusing the formula\n\\[\n\\begin{array}{lll}\nR_{p} & = & \\dfrac{1}{1/R_{1}+1/R_{2}}\n\\end{array}\n\\]\nResistance values are usually known only up to some\ntolerance guaranteed by the manufacturer of the resistor.\n\nFor example, if\nyou buy a resistor labeled 6.8 ohms with 10% tolerance you can\nonly be sure that the resistor has a resistance between\n$6.8-0.68=6.12$ and\n$6.8+0.68=7.48$ ohms.\n\nThus, if you have a\n6.8-ohm 10% resistor in parallel with a 4.7-ohm\n5% resistor, the resistance of the combination can range from about\n2.58 ohms (if the two resistors are at the lower bounds) to about 2.97 ohms\n(if the two resistors are at the upper bounds).\n\nAlyssa s idea is to implement interval arithmetic as a\nset of arithmetic operations for combining intervals (objects\nthat represent the range of possible values of an inexact quantity).\n\nThe\nresult of adding, subtracting, multiplying, or dividing two intervals is\nitself an interval, representing the range of the result.\n\nAlyssa postulates the existence of an abstract object called an\ninterval that has two endpoints: a lower bound and an upper bound.\n\nShe also presumes that, given the endpoints of an interval, she can\nconstruct the interval using the data constructor\nmake_interval.\n\nAlyssa first writes a\nfunction\nfor adding two intervals.", + "token_count": 297, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Introduction to Data Abstraction", @@ -420,8 +450,8 @@ "chunk_id": "Building_Abstractions_with_Data_Extended_Exercise_Interval_Arithmetic_1" }, { - "content": "Alyssa first writes a\nfunction\nfor adding two intervals.\n\nShe reasons that the minimum value the sum could\nbe is the sum of the two lower bounds and the maximum value it could be is\nthe sum of the two upper bounds:\n\n```javascript\nadd_interval\n make_interval\n print_interval\n [ 4, 7 ]\n add_interval_example\n\nfunction add_interval(x, y) {\n return make_interval(lower_bound(x) + lower_bound(y),\n upper_bound(x) + upper_bound(y));\n}\n```\n\nAlyssa also works out the product of two intervals by finding the\nminimum and the maximum of the products of the bounds and using them\nas the bounds of the resulting interval.\n\n(The functions math_min\nand\nmath_max\nare\nprimitives that find the minimum or maximum of any number of arguments.)\n\n```javascript\nmul_interval\n make_interval\n print_interval\n '[ 3 , 10 ]'\n mul_interval_example\n\nfunction mul_interval(x, y) {\n const p1 = lower_bound(x) * lower_bound(y);\n const p2 = lower_bound(x) * upper_bound(y);\n const p3 = upper_bound(x) * lower_bound(y);\n const p4 = upper_bound(x) * upper_bound(y);\n return make_interval(math_min(p1, p2, p3, p4),\n math_max(p1, p2, p3, p4));\n}\n```\n\nTo divide two intervals, Alyssa multiplies the first by the reciprocal of\nthe second.\n\nNote that the bounds of the reciprocal interval are\nthe reciprocal of the upper bound and the reciprocal of the lower bound, in\nthat order.\n\n```javascript\ndiv_interval\n mul_interval\n print_interval\n '[ 0.2 , 0.6666666666666666 ]'\n div_interval_example\n\nfunction div_interval(x, y) {\n return mul_interval(x, make_interval(1 / upper_bound(y),\n 1 / lower_bound(y)));\n}\n```\n\nAfter debugging her program, Alyssa shows it to a potential user, who\ncomplains that her program solves the wrong problem.\n\nHe wants a program\nthat can deal with numbers represented as a center value and an additive\ntolerance; for example, he wants to work with intervals such as\n$3.5\\pm 0.15$ rather than\n$[3.35, 3.65]$.\n\nAlyssa returns to her desk and\nfixes this problem by supplying an alternate constructor and alternate\nselectors:", - "token_count": 297, + "content": "Alyssa first writes a\nfunction\nfor adding two intervals.\n\n```javascript\nadd_interval\n make_interval\n print_interval\n [ 4, 7 ]\n add_interval_example\n\nfunction add_interval(x, y) {\n return make_interval(lower_bound(x) + lower_bound(y),\n upper_bound(x) + upper_bound(y));\n}\n```\n\nAlyssa also works out the product of two intervals by finding the\nminimum and the maximum of the products of the bounds and using them\nas the bounds of the resulting interval.\n\n(The functions math_min\nand\nmath_max\nare\nprimitives that find the minimum or maximum of any number of arguments.)\n\n```javascript\nmul_interval\n make_interval\n print_interval\n '[ 3 , 10 ]'\n mul_interval_example\n\nfunction mul_interval(x, y) {\n const p1 = lower_bound(x) * lower_bound(y);\n const p2 = lower_bound(x) * upper_bound(y);\n const p3 = upper_bound(x) * lower_bound(y);\n const p4 = upper_bound(x) * upper_bound(y);\n return make_interval(math_min(p1, p2, p3, p4),\n math_max(p1, p2, p3, p4));\n}\n```\n\nTo divide two intervals, Alyssa multiplies the first by the reciprocal of\nthe second.\n\nNote that the bounds of the reciprocal interval are\nthe reciprocal of the upper bound and the reciprocal of the lower bound, in\nthat order.\n\n```javascript\ndiv_interval\n mul_interval\n print_interval\n '[ 0.2 , 0.6666666666666666 ]'\n div_interval_example\n\nfunction div_interval(x, y) {\n return mul_interval(x, make_interval(1 / upper_bound(y),\n 1 / lower_bound(y)));\n}\n```\n\nAfter debugging her program, Alyssa shows it to a potential user, who\ncomplains that her program solves the wrong problem.\n\nHe wants a program\nthat can deal with numbers represented as a center value and an additive\ntolerance; for example, he wants to work with intervals such as\n$3.5\\pm 0.15$ rather than\n$[3.35, 3.65]$.\n\nAlyssa returns to her desk and\nfixes this problem by supplying an alternate constructor and alternate\nselectors:\n\n```javascript\nmake_center_width\n make_interval\n make_center_width_example\n 0.5\n\nfunction make_center_width(c, w) {\n return make_interval(c - w, c + w);\n}\nfunction center(i) {\n return (lower_bound(i) + upper_bound(i)) / 2;\n}\nfunction width(i) {\n return (upper_bound(i) - lower_bound(i)) / 2;\n}\n```", + "token_count": 302, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Introduction to Data Abstraction", @@ -430,8 +460,8 @@ "chunk_id": "Building_Abstractions_with_Data_Extended_Exercise_Interval_Arithmetic_2" }, { - "content": "Alyssa returns to her desk and\nfixes this problem by supplying an alternate constructor and alternate\nselectors:\n\n```javascript\nmake_center_width\n make_interval\n make_center_width_example\n 0.5\n\nfunction make_center_width(c, w) {\n return make_interval(c - w, c + w);\n}\nfunction center(i) {\n return (lower_bound(i) + upper_bound(i)) / 2;\n}\nfunction width(i) {\n return (upper_bound(i) - lower_bound(i)) / 2;\n}\n```\n\n```javascript\nmake_center_width_example\n make_interval\n\nconst my_interval = make_center_width(1, 0.5);\nwidth(my_interval);\n```\n\nUnfortunately, most of Alyssa s users are engineers.\n\nReal engineering\nsituations usually involve measurements with only a small uncertainty,\nmeasured as the ratio of the width of the interval to the midpoint of the\ninterval.\n\nEngineers usually specify percentage tolerances on the parameters\nof devices, as in the resistor specifications given earlier.\n\nAfter considerable work, Alyssa P.\n\nHacker delivers her finished\nsystem.\n\nSeveral years later, after she has forgotten all about it, she\ngets a frenzied call from an irate user, Lem E.\n\nTweakit.\n\nIt seems that Lem has\nnoticed that the\n\\[\n\\dfrac{R_{1}R_{2}}{R_{1}+R_{2}}\n\\]\nand\n\\[\n\\dfrac{1}{1/R_{1}+1/R_{2}}\n\\]\nHe has written the following two programs, each of which computes the\nparallel-resistors formula differently:\n\n```javascript\npar\n add_interval\n mul_interval\n div_interval\n print_interval\n par_example\n '[ 2 , 4.363636363636363 ][ 2.5454545454545454 , 3.428571428571429 ]'\n\nfunction par1(r1, r2) {\n return div_interval(mul_interval(r1, r2),\n add_interval(r1, r2));\n}\nfunction par2(r1, r2) {\n const one = make_interval(1, 1);\n return div_interval(one,\n add_interval(div_interval(one, r1),\n div_interval(one, r2)));\n}\n```\n\n```javascript\npar_example\n\ndisplay(print_interval(par1(pair(4, 6), pair(7, 8))));\n\ndisplay(print_interval(par2(pair(4, 6), pair(7, 8))));\n\nprint_interval(par1(pair(4, 6), pair(7, 8)))\n+\nprint_interval(par2(pair(4, 6), pair(7, 8)));\n```\n\nLem complains that Alyssa s program gives different answers for\nthe two ways of computing.\n\nThis is a serious complaint.", - "token_count": 262, + "content": "Alyssa returns to her desk and\nfixes this problem by supplying an alternate constructor and alternate\nselectors:\n\nUnfortunately, most of Alyssa s users are engineers.\n\nReal engineering\nsituations usually involve measurements with only a small uncertainty,\nmeasured as the ratio of the width of the interval to the midpoint of the\ninterval.\n\nEngineers usually specify percentage tolerances on the parameters\nof devices, as in the resistor specifications given earlier.\n\nAfter considerable work, Alyssa P.\n\nHacker delivers her finished\nsystem.\n\nSeveral years later, after she has forgotten all about it, she\ngets a frenzied call from an irate user, Lem E.\n\nTweakit.\n\nIt seems that Lem has\nnoticed that the\nformula for parallel resistors can be written in two\nalgebraically equivalent ways:\n\\[\n\\dfrac{R_{1}R_{2}}{R_{1}+R_{2}}\n\\]\nand\n\\[\n\\dfrac{1}{1/R_{1}+1/R_{2}}\n\\]\nHe has written the following two programs, each of which computes the\nparallel-resistors formula differently:\n\n```javascript\npar\n add_interval\n mul_interval\n div_interval\n print_interval\n par_example\n '[ 2 , 4.363636363636363 ][ 2.5454545454545454 , 3.428571428571429 ]'\n\nfunction par1(r1, r2) {\n return div_interval(mul_interval(r1, r2),\n add_interval(r1, r2));\n}\nfunction par2(r1, r2) {\n const one = make_interval(1, 1);\n return div_interval(one,\n add_interval(div_interval(one, r1),\n div_interval(one, r2)));\n}\n```\n\n```javascript\npar_example\n\ndisplay(print_interval(par1(pair(4, 6), pair(7, 8))));\n\ndisplay(print_interval(par2(pair(4, 6), pair(7, 8))));\n\nprint_interval(par1(pair(4, 6), pair(7, 8)))\n+\nprint_interval(par2(pair(4, 6), pair(7, 8)));\n```\n\nLem complains that Alyssa s program gives different answers for\nthe two ways of computing.\n\nThis is a serious complaint.", + "token_count": 226, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Introduction to Data Abstraction", @@ -440,9 +470,9 @@ "chunk_id": "Building_Abstractions_with_Data_Extended_Exercise_Interval_Arithmetic_3" }, { - "content": "We began the rational-number implementation in section by implementing the rational-number operations add_rat, sub_rat, and so on in terms of three unspecified functions: make_rat, numerators,\n\ndenominators, and rational numbers whose behavior was specified by the latter three functions.\n\nBut exactly what is meant by data ?\n\nIt is not enough to say\nwhatever is implemented by the given selectors and\nconstructors.\n\nClearly, not every arbitrary set of three\nfunctions\ncan serve as an appropriate basis for the rational-number\nimplementation.\n\nWe need to guarantee that,\nmake_rat,\nmake_rat(n, d),\nthen\n\n```javascript\n\\[\n\t \\begin{array}{lll}\n \\dfrac{\\texttt{numer}(\\texttt{x})}{\\texttt{denom}(\\texttt{x})}\n &=&\n \\dfrac{\\texttt{n}}{\\texttt{d}}\n\t \\end{array}\n \\]\n```\n\nIn fact, this is the only condition make_rat, functions must fulfill in order to be a valid representation.\n\nThis point of view can serve to define not only\nhigh-level data objects, such as rational numbers, but\nlower-level objects as well.\n\nConsider the notion of a\npair, which we used in order to define our\nrational numbers.\n\nWe never actually said what a pair was, only that\nthe language supplied\nfunctions\npair,\nhead,\nand\ntail\nfor operating on pairs.\n\nBut the only thing we need to know about these\nthree operations\nis that if we glue two objects together using\npair\nwe can retrieve the objects using\nhead\nand\ntail.\npair(x, y)\nthen\nhead(z)\nis\ntail(z)\nis\nfunctions\nare included as primitives in our language.\n\nHowever, any triple of\nfunctions\nthat satisfies the above condition can be used as the basis for\nimplementing pairs.\n\nThis point is illustrated strikingly by the fact\nthat we could implement\npair,\nhead,\nand\ntail\nwithout using any data structures at all but only using\nfunctions.\n\nHere are the definitions:", - "token_count": 271, - "has_code": true, + "content": "We began the rational-number implementation in\nsection by implementing the\nrational-number operations\nadd_rat,\nsub_rat,\nand so on in terms of three unspecified\nfunctions:\nmake_rat,\n, and.\n\nAt that point, we could think of the\noperations as being defined in terms of data objects numerators,\ndenominators, and rational numbers whose behavior was specified\nby the latter three\nfunctions.\n\nBut exactly what is meant by data ?\n\nIt is not enough to say\nwhatever is implemented by the given selectors and\nconstructors.\n\nClearly, not every arbitrary set of three\nfunctions\ncan serve as an appropriate basis for the rational-number\nimplementation.\n\nWe need to guarantee that,\nif we construct a rational number from a\npair of integers and\n, then extracting the\nand the\nof and\ndividing them should yield the same result as dividing\nby.\n\nIn\nother words,\nmake_rat,\n, and\nmust satisfy the condition that, for\nany integer and any nonzero\ninteger , if is\nmake_rat(n, d),\nthen\n\\[ \\begin{array}{lll} \\dfrac{\\texttt{numer}(\\texttt{x})}{\\texttt{denom}(\\texttt{x})} &=& \\dfrac{\\texttt{n}}{\\texttt{d}} \\end{array} \\]\nIn fact, this is the only condition\nmake_rat,\n, and\nmust fulfill in order to form a\nsuitable basis for a rational-number representation.\n\nIn general, we can\nthink of data as defined by some collection of selectors and\nconstructors, together with specified conditions that these\nfunctions\nmust fulfill in order to be a valid\nrepresentation.\n\nThis point of view can serve to define not only\nhigh-level data objects, such as rational numbers, but\nlower-level objects as well.\n\nConsider the notion of a\npair, which we used in order to define our\nrational numbers.\n\nWe never actually said what a pair was, only that\nthe language supplied\nfunctions\npair,\nhead,\nand\ntail\nfor operating on pairs.", + "token_count": 279, + "has_code": false, "chapter": "Building Abstractions with Data", "section": "Introduction to Data Abstraction", "subsection": "What Is Meant by Data?", @@ -450,8 +480,8 @@ "chunk_id": "Building_Abstractions_with_Data_What_Is_Meant_by_Data_1" }, { - "content": "Here are the definitions:\n\n```javascript\ncons_with_dispatch\n cons_1_2_run\n 1\n\nfunction pair(x, y) {\n function dispatch(m) {\n return m === 0\n ? x\n : m === 1\n ? y\n : error(m, \"argument not 0 or 1 -- pair\");\n }\n return dispatch;\n}\nfunction head(z) { return z(0); }\n\nfunction tail(z) { return z(1); }\n```\n\n```javascript\ncons_1_2_run\n\nconst x = pair(1, 2);\nhead(x);\n```\n\nThis use of\nfunctions\ncorresponds to nothing like our intuitive notion of what data should be.\n\nNevertheless, all we need to do to show that this is a valid way to\nrepresent pairs is to verify that these\nfunctions\nsatisfy the condition given above.\n\nThe subtle point to notice is that the value returned by pair(x, y) is a functionnamely the internally defined function head(z) is defined to apply\n\nfunction formed by pair(x, y), then head(pair(x, y)) yields tail(pair(x, y)) applies the function returned by pair(x, y) to 1, which returns functional implementation of\n\npairs is a valid implementation, and if we access pairs using only pair, head, and tail we cannot distinguish this implementation from one that uses\n\nreal data structures.\n\nThe point of exhibiting the functional representation of pairs is not that our language works this way\n\n```javascript\n(an efficient implementation of pairs\n might use JavaScript's native\n vector data structure)\n```\n\nbut that it could work this way.\n\nThe\nfunctional\nrepresentation, although obscure, is a perfectly adequate way to represent\npairs, since it fulfills the only conditions that pairs need to fulfill.\n\nThis example also demonstrates that the ability to manipulate\nfunctions\nas objects automatically provides the ability to represent compound data.\n\nThis may seem a curiosity now, but\nfunctional\nrepresentations of data will play a central role in our programming\nrepertoire.", - "token_count": 286, + "content": "We never actually said what a pair was, only that\nthe language supplied\nfunctions\npair,\nhead,\nand\ntail\nfor operating on pairs.\n\nThat is, the operations satisfy the condition that, for any objects\nand , if\nis\npair(x, y)\nthen\nhead(z)\nis and\ntail(z)\nis.\n\nIndeed, we mentioned that these three\nfunctions\nare included as primitives in our language.\n\nHowever, any triple of\nfunctions\nthat satisfies the above condition can be used as the basis for\nimplementing pairs.\n\nThis point is illustrated strikingly by the fact\nthat we could implement\npair,\nhead,\nand\ntail\nwithout using any data structures at all but only using\nfunctions.\n\nHere are the definitions:\n\n```javascript\ncons_with_dispatch\n cons_1_2_run\n 1\n\nfunction pair(x, y) {\n function dispatch(m) {\n return m === 0\n ? x\n : m === 1\n ? y\n : error(m, \"argument not 0 or 1 -- pair\");\n }\n return dispatch;\n}\nfunction head(z) { return z(0); }\n\nfunction tail(z) { return z(1); }\n```\n\n```javascript\ncons_1_2_run\n\nconst x = pair(1, 2);\nhead(x);\n```\n\nThis use of\nfunctions\ncorresponds to nothing like our intuitive notion of what data should be.\n\nNevertheless, all we need to do to show that this is a valid way to\nrepresent pairs is to verify that these\nfunctions\nsatisfy the condition given above.\n\nThe subtle point to notice is that the value returned by\npair(x, y)\nis a\nfunctionnamely\nthe internally defined\nfunction\n, which takes one argument and returns\neither or\ndepending on whether the argument is 0 or 1.\n\nCorrespondingly,\nhead(z)\nis defined to apply to 0.\n\nHence, if\nis the\nfunction\nformed by\npair(x, y),\nthen applied to 0 will yield.\n\nThus, we have shown that\nhead(pair(x, y))\nyields , as desired.\n\nSimilarly,\ntail(pair(x, y))\napplies the\nfunction\nreturned by\npair(x, y)\nto 1, which returns.", + "token_count": 297, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Introduction to Data Abstraction", @@ -460,8 +490,8 @@ "chunk_id": "Building_Abstractions_with_Data_What_Is_Meant_by_Data_2" }, { - "content": "This may seem a curiosity now, but\nfunctional\nrepresentations of data will play a central role in our programming\nrepertoire.\n\nThis style of programming is often called\nmessage passing , and we will be using it as a basic tool in\nchapter when we address the issues of modeling and simulation.\n\nDefine\nadd_1).\n\n(Hint: Use substitution to evaluate\nadd_1(zero)).\n\nGive a direct definition of the addition\nfunction plus\n(not in terms of repeated application of\nadd_1).", - "token_count": 76, + "content": "Similarly,\ntail(pair(x, y))\napplies the\nfunction\nreturned by\npair(x, y)\nto 1, which returns.\n\nThe point of exhibiting the\nfunctional\nrepresentation of pairs is not that our language works this way\n(an efficient implementation of pairs might use JavaScript's native vector data structure)\nbut that it could work this way.\n\nThe\nfunctional\nrepresentation, although obscure, is a perfectly adequate way to represent\npairs, since it fulfills the only conditions that pairs need to fulfill.\n\nThis example also demonstrates that the ability to manipulate\nfunctions\nas objects automatically provides the ability to represent compound data.\n\nThis may seem a curiosity now, but\nfunctional\nrepresentations of data will play a central role in our programming\nrepertoire.\n\nThis style of programming is often called\nmessage passing , and we will be using it as a basic tool in\nchapter when we address the issues of modeling and simulation.\n\nDefine and\ndirectly (not in terms of and\nadd_1).\n\n(Hint: Use substitution to evaluate\nadd_1(zero)).\n\nGive a direct definition of the addition\nfunction plus\n(not in terms of repeated application of\nadd_1).", + "token_count": 177, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Introduction to Data Abstraction", @@ -470,8 +500,8 @@ "chunk_id": "Building_Abstractions_with_Data_What_Is_Meant_by_Data_3" }, { - "content": "We have introduced data abstraction, a methodology for structuring systems\nin such a way that much of a program can be specified independent of the\nchoices involved in implementing the data objects that the program\nmanipulates.\n\nFor example, we saw in\nsection how to separate the task of\ndesigning a program that uses rational numbers from the task of implementing\nrational numbers in terms of the computer language s primitive\nmechanisms for constructing compound data.\n\nThe key idea was to erect an\nin this case, the selectors and constructors for\nrational numbers\n(make_rat,\nthat isolates the way rational\nnumbers are used from their underlying representation in terms of list\nstructure.\n\nA similar abstraction barrier isolates the details of the\nfunctions\nthat perform rational arithmetic\n(add_rat,\nsub_rat,\nmul_rat,\nand\ndiv_rat)\nfrom the higher-level\nfunctions\nthat use rational numbers.\n\nThe resulting program has the structure shown\nin figure.\n\nThese data-abstraction barriers are powerful tools for controlling\ncomplexity.\n\nBy isolating the underlying representations of data\nobjects, we can divide the task of designing a large program into\nsmaller tasks that can be performed separately.\n\nBut this kind of data\nabstraction is not yet powerful enough, because it may not always make\nsense to speak of the underlying representation for a\ndata object.\n\nFor one thing, there might be more than one useful representation for\na data object, and we might like to design systems that can deal with\nmultiple representations.\n\nTo take a simple example, complex numbers\nmay be represented in two almost equivalent ways: in rectangular form\n(real and imaginary parts) and in polar form (magnitude and angle).\n\nSometimes rectangular form is more appropriate and sometimes polar\nform is more appropriate.", - "token_count": 279, + "content": "We have introduced data abstraction, a methodology for structuring systems\nin such a way that much of a program can be specified independent of the\nchoices involved in implementing the data objects that the program\nmanipulates.\n\nFor example, we saw in\nsection how to separate the task of\ndesigning a program that uses rational numbers from the task of implementing\nrational numbers in terms of the computer language s primitive\nmechanisms for constructing compound data.\n\nThe key idea was to erect an\nabstraction barrier in this case, the selectors and constructors for\nrational numbers\n(make_rat,\n,\n) that isolates the way rational\nnumbers are used from their underlying representation in terms of list\nstructure.\n\nA similar abstraction barrier isolates the details of the\nfunctions\nthat perform rational arithmetic\n(add_rat,\nsub_rat,\nmul_rat,\nand\ndiv_rat)\nfrom the higher-level\nfunctions\nthat use rational numbers.\n\nThe resulting program has the structure shown\nin figure.\n\nThese data-abstraction barriers are powerful tools for controlling\ncomplexity.\n\nBy isolating the underlying representations of data\nobjects, we can divide the task of designing a large program into\nsmaller tasks that can be performed separately.\n\nBut this kind of data\nabstraction is not yet powerful enough, because it may not always make\nsense to speak of the underlying representation for a\ndata object.\n\nFor one thing, there might be more than one useful representation for\na data object, and we might like to design systems that can deal with\nmultiple representations.\n\nTo take a simple example, complex numbers\nmay be represented in two almost equivalent ways: in rectangular form\n(real and imaginary parts) and in polar form (magnitude and angle).\n\nSometimes rectangular form is more appropriate and sometimes polar\nform is more appropriate.", + "token_count": 283, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", @@ -480,8 +510,8 @@ "chunk_id": "Building_Abstractions_with_Data_Multiple_Representations_for_Abstract_Data_1" }, { - "content": "Sometimes rectangular form is more appropriate and sometimes polar\nform is more appropriate.\n\nIndeed, it is perfectly plausible to\nimagine a system in which complex numbers are represented in both\nways, and in which the\nfunctions\nfor manipulating complex numbers work with either representation.\n\nMore importantly, programming systems are often designed by many\npeople working over extended periods of time, subject to requirements\nthat change over time.\n\nIn such an environment, it is simply not\npossible for everyone to agree in advance on choices of data\nrepresentation.\n\nSo in addition to the data-abstraction barriers that\nisolate representation from use, we need abstraction barriers that\nisolate different design choices from each other and permit different\nchoices to coexist in a single program.\n\nFurthermore, since large\nprograms are often created by combining\npreexisting\nmodules that were\ndesigned in isolation, we need conventions that permit programmers to\nincorporate modules into larger systems\nadditively , that is,\nwithout having to redesign or reimplement these modules.\n\nIn this section, we will learn how to cope with data that may be\nrepresented in different ways by different parts of a program.\n\nThis\nrequires constructing\ngeneric functions functions\nthat can operate on data that may be represented in more than one way.\n\nOur\nmain technique for building generic\nfunctions\nwill be to work in terms of data objects that have\ntype tags , that is, data objects that include explicit information\nabout how they are to be processed.\n\nWe will also discuss\ndata-directed programming, a powerful and convenient\nimplementation strategy for additively assembling systems with generic\noperations.\n\nWe begin with the simple complex-number example.\n\nWe will see how\ntype tags and data-directed style enable us to design separate\nrectangular and polar representations for complex numbers while\nmaintaining the notion of an abstract\ncomplex-number\ndata object.", - "token_count": 298, + "content": "Sometimes rectangular form is more appropriate and sometimes polar\nform is more appropriate.\n\nMore importantly, programming systems are often designed by many\npeople working over extended periods of time, subject to requirements\nthat change over time.\n\nIn such an environment, it is simply not\npossible for everyone to agree in advance on choices of data\nrepresentation.\n\nSo in addition to the data-abstraction barriers that\nisolate representation from use, we need abstraction barriers that\nisolate different design choices from each other and permit different\nchoices to coexist in a single program.\n\nFurthermore, since large\nprograms are often created by combining\npreexisting\nmodules that were\ndesigned in isolation, we need conventions that permit programmers to\nincorporate modules into larger systems\nadditively , that is,\nwithout having to redesign or reimplement these modules.\n\nIn this section, we will learn how to cope with data that may be\nrepresented in different ways by different parts of a program.\n\nThis\nrequires constructing\ngeneric functions functions\nthat can operate on data that may be represented in more than one way.\n\nOur\nmain technique for building generic\nfunctions\nwill be to work in terms of data objects that have\ntype tags , that is, data objects that include explicit information\nabout how they are to be processed.\n\nWe will also discuss\ndata-directed programming, a powerful and convenient\nimplementation strategy for additively assembling systems with generic\noperations.\n\nWe begin with the simple complex-number example.\n\nWe will see how\ntype tags and data-directed style enable us to design separate\nrectangular and polar representations for complex numbers while\nmaintaining the notion of an abstract\ncomplex-number\ndata object.", + "token_count": 267, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", @@ -490,9 +520,9 @@ "chunk_id": "Building_Abstractions_with_Data_Multiple_Representations_for_Abstract_Data_2" }, { - "content": "We will see how\ntype tags and data-directed style enable us to design separate\nrectangular and polar representations for complex numbers while\nmaintaining the notion of an abstract\ncomplex-number\ndata object.\n\nWe will accomplish this by defining arithmetic\nfunctions\nfor complex numbers\n(add_complex,\nsub_complex,\nmul_complex,\nand\ndiv_complex)\nin terms of generic selectors that access parts of a complex number\nindependent of how the number is represented.\n\nThe resulting complex-number\nsystem, as shown in\nfigure,\ncontains two different kinds of\nhorizontal abstraction barriers\nplay the same role as the ones in\nfigure.\n\nThey isolate\nhigher-level operations from lower-level\nrepresentations.\n\nIn addition, there is a vertical barrier\nthat gives us the ability to separately design and install alternative\nrepresentations.\n\n```javascript\nData-abstraction barriers in the\n\t complex-number system.\n```\n\nIn section we will show how to\nuse type tags and data-directed style to develop a generic arithmetic\npackage.\n\nThis provides\nfunctions\n( numbers and\ncan be easily extended when a new kind of number is needed.\n\nIn\nsection , we ll show how to\nuse generic arithmetic in a system that performs symbolic algebra.", - "token_count": 180, - "has_code": true, + "content": "We will see how\ntype tags and data-directed style enable us to design separate\nrectangular and polar representations for complex numbers while\nmaintaining the notion of an abstract\ncomplex-number\ndata object.\n\nThe resulting complex-number\nsystem, as shown in\nfigure,\ncontains two different kinds of\nabstraction barriers.\n\nThe horizontal abstraction barriers\nplay the same role as the ones in\nfigure.\n\nThey isolate\nhigher-level operations from lower-level\nrepresentations.\n\nIn addition, there is a vertical barrier\nthat gives us the ability to separately design and install alternative\nrepresentations.\n\nData-abstraction barriers in the complex-number system.\n\nIn section we will show how to\nuse type tags and data-directed style to develop a generic arithmetic\npackage.\n\nThis provides\nfunctions\n(, , and so\non) that can be used to manipulate all sorts of numbers and\ncan be easily extended when a new kind of number is needed.\n\nIn\nsection , we ll show how to\nuse generic arithmetic in a system that performs symbolic algebra.", + "token_count": 159, + "has_code": false, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", "subsection": null, @@ -500,8 +530,8 @@ "chunk_id": "Building_Abstractions_with_Data_Multiple_Representations_for_Abstract_Data_3" }, { - "content": "The general strategy of checking the type of a datum and calling an\nappropriate\nfunction\nis called\ndispatching on type.\n\nThis is a powerful strategy for obtaining\nmodularity in system design.\n\nOn the other hand, implementing the dispatch\nas in section has two significant\nweaknesses.\n\nOne weakness is that the generic interface\nfunctions\n(real_part,\nimag_part,\nfunctions\nto check for the new type and apply the appropriate selector for that\nrepresentation.\n\nAnother weakness of the technique is that even though the individual\nrepresentations can be designed separately, we must guarantee that no two\nfunctions\nin the entire system have the same name.\n\nThis is why Ben and Alyssa had\nto change the names of their original\nfunctions\nfrom section.\n\nThe issue underlying both of these weaknesses is that the technique for\nimplementing generic interfaces is not additive.\n\nThe person\nimplementing the generic selector\nfunctions\nmust modify those\nfunctions\neach time a new representation is installed, and the people\ninterfacing the individual representations must modify their\ncode to avoid name conflicts.\n\nIn each of these cases, the changes\nthat must be made to the code are straightforward, but they must be\nmade nonetheless, and this is a source of inconvenience and error.\n\nThis is not much of a problem for the complex-number system as it\nstands, but suppose there were not two but hundreds of different\nrepresentations for complex numbers.\n\nAnd suppose that there were many\ngeneric selectors to be maintained in the abstract-data interface.\n\nSuppose, in fact, that no one programmer knew all the interface\nfunctions\nor all the representations.\n\nThe problem is real and must\nbe addressed in such programs as\nlarge-scale data-base-management systems.\n\nWhat we need is a means for modularizing the system design even\nfurther.\n\nThis is provided by the programming technique known as\ndata-directed programming.", - "token_count": 298, + "content": "The general strategy of checking the type of a datum and calling an\nappropriate\nfunction\nis called\ndispatching on type.\n\nThis is a powerful strategy for obtaining\nmodularity in system design.\n\nOn the other hand, implementing the dispatch\nas in section has two significant\nweaknesses.\n\nOne weakness is that the generic interface\nfunctions\n(real_part,\nimag_part,\n, and\n) must know about all the different\nrepresentations.\n\nFor instance, suppose we wanted to incorporate a new\nrepresentation for complex numbers into our complex-number system.\n\nWe\nwould need to identify this new representation with a type, and then add a\nclause to each of the generic interface\nfunctions\nto check for the new type and apply the appropriate selector for that\nrepresentation.\n\nAnother weakness of the technique is that even though the individual\nrepresentations can be designed separately, we must guarantee that no two\nfunctions\nin the entire system have the same name.\n\nThis is why Ben and Alyssa had\nto change the names of their original\nfunctions\nfrom section.\n\nThe issue underlying both of these weaknesses is that the technique for\nimplementing generic interfaces is not additive.\n\nThe person\nimplementing the generic selector\nfunctions\nmust modify those\nfunctions\neach time a new representation is installed, and the people\ninterfacing the individual representations must modify their\ncode to avoid name conflicts.\n\nIn each of these cases, the changes\nthat must be made to the code are straightforward, but they must be\nmade nonetheless, and this is a source of inconvenience and error.\n\nThis is not much of a problem for the complex-number system as it\nstands, but suppose there were not two but hundreds of different\nrepresentations for complex numbers.\n\nAnd suppose that there were many\ngeneric selectors to be maintained in the abstract-data interface.", + "token_count": 292, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", @@ -510,9 +540,9 @@ "chunk_id": "Building_Abstractions_with_Data_Data-Directed_Programming_and_Additivity_1" }, { - "content": "This is provided by the programming technique known as\ndata-directed programming.\n\nTo understand how data-directed\nprogramming works, begin with the observation that whenever we deal\nwith a set of generic operations that are common to a set of\ndifferent types we are, in effect, dealing with a two-dimensional\ntable that contains the possible operations on one axis and the\npossible types on the other axis.\n\nThe entries in the table are the\nfunctions\nthat implement each operation for each type of argument presented.\n\nIn the complex-number system developed in the previous section, the\ncorrespondence between operation name, data type, and actual\nfunction\nwas spread out among the various conditional clauses in the generic\ninterface\nfunctions.\n\nBut the same information could have been organized in a table, as shown in\nfigure.\n\nData-directed programming is the technique of designing programs to work\nwith such a\nfunctions\nthat each perform an explicit dispatch on type.\n\nHere we will implement the\ninterface as a single\nfunction\nthat looks up the combination of the operation name and argument type in\nthe table to find the correct\nfunction\nto apply, and then applies it to the contents of the argument.\n\nIf we do\nthis, then to add a new representation package to the system we need not\nchange any existing\n\n```javascript\nfunctions;\n```\n\nwe need only add new entries to the table.\n\nTable of operations for the complex-number system.\n\nTo implement this plan, assume that we have two\nfunctions,\n-\n-\nput(op, type, item)\ninstalls the\nitem\nin the table, indexed by the\n\n```javascript\nop and the\n\t type.\n```\n\n- - get(op, type) looks up the\n\n```javascript\nop,\n\t type\n```\n\nentry in the table and returns the item found there.\n\nIf no item is found,", - "token_count": 289, - "has_code": true, + "content": "And suppose that there were many\ngeneric selectors to be maintained in the abstract-data interface.\n\nThe problem is real and must\nbe addressed in such programs as\nlarge-scale data-base-management systems.\n\nWhat we need is a means for modularizing the system design even\nfurther.\n\nThis is provided by the programming technique known as\ndata-directed programming.\n\nTo understand how data-directed\nprogramming works, begin with the observation that whenever we deal\nwith a set of generic operations that are common to a set of\ndifferent types we are, in effect, dealing with a two-dimensional\ntable that contains the possible operations on one axis and the\npossible types on the other axis.\n\nThe entries in the table are the\nfunctions\nthat implement each operation for each type of argument presented.\n\nIn the complex-number system developed in the previous section, the\ncorrespondence between operation name, data type, and actual\nfunction\nwas spread out among the various conditional clauses in the generic\ninterface\nfunctions.\n\nBut the same information could have been organized in a table, as shown in\nfigure.\n\nData-directed programming is the technique of designing programs to work\nwith such a\ntable directly.\n\nPreviously, we implemented the mechanism that\ninterfaces the complex-arithmetic code with the two representation packages\nas a set of\nfunctions\nthat each perform an explicit dispatch on type.\n\nHere we will implement the\ninterface as a single\nfunction\nthat looks up the combination of the operation name and argument type in\nthe table to find the correct\nfunction\nto apply, and then applies it to the contents of the argument.\n\nIf we do\nthis, then to add a new representation package to the system we need not\nchange any existing\nfunctions;\nwe need only add new entries to the table.\n\nTable of operations for the complex-number system.", + "token_count": 295, + "has_code": false, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", "subsection": "Data-Directed Programming and Additivity", @@ -520,8 +550,8 @@ "chunk_id": "Building_Abstractions_with_Data_Data-Directed_Programming_and_Additivity_2" }, { - "content": "If no item is found,\n\n```javascript\na unique primitive value that is referred to by the name\n\t undefined and recognized\n\t by the primitive predicate\n\t is_undefined.\n```\n\nFor now, we can assume that (section ) we will see how to implement these and other operations for manipulating tables.\n\nHere is how data-directed programming can be used in the complex-number\nsystem.\n\nBen, who developed the rectangular representation, implements his\ncode just as he did originally.\n\nHe defines a collection of\nfunctions\nor a\npackage , and interfaces these to the rest of the system by adding\nentries to the table that tell the system how to operate on rectangular\nnumbers.\n\nThis is accomplished by calling the following\nfunction:\n\n```javascript\noperation_table_from_chapter_3\n\n// operation_table, put and get\n// from chapter 3 (section 3.3.3)\n```\n\n```javascript\ninstall_rectangular_package_usage\n\ninstall_rectangular_package();\n```\n\n```javascript\ninstall_rectangular_package\n operation_table_from_chapter_3\n operation_table\n attach_tag\n square_definition\n 'done'\n install_rectangular_package_usage\n\nfunction install_rectangular_package() {\n // internal functions\n function real_part(z) { return head(z); }\n function imag_part(z) { return tail(z); }\n function make_from_real_imag(x, y) { return pair(x, y); }\n function magnitude(z) {\n return math_sqrt(square(real_part(z)) + square(imag_part(z)));\n }\n function angle(z) {\n return math_atan2(imag_part(z), real_part(z));\n }\n function make_from_mag_ang(r, a) {\n return pair(r * math_cos(a), r * math_sin(a));\n }\n\n // interface to the rest of the system\n function tag(x) { return attach_tag(\"rectangular\", x); }\n put(\"real_part\", list(\"rectangular\"), real_part);\n put(\"imag_part\", list(\"rectangular\"), imag_part);\n put(\"magnitude\", list(\"rectangular\"), magnitude);\n put(\"angle\", list(\"rectangular\"), angle);\n put(\"make_from_real_imag\", \"rectangular\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"rectangular\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\n```\n\nNotice that the internal\nfunctions\nhere are the same\nfunctions\nfrom section that\nBen wrote when he was working in isolation.\n\nNo changes are necessary in\norder to interface them to the rest of the system.\n\nMoreover, since these\nfunction declarations\nare internal to the installation\nfunction,\nBen needn t worry about name conflicts with other\nfunctions\noutside the rectangular package.", - "token_count": 301, + "content": "Table of operations for the complex-number system.\n\nIf no item is found,\nreturns\na unique primitive value that is referred to by the name undefined and recognized by the primitive predicate is_undefined.\n\nFor now, we can assume that and\nare included in our language.\n\nIn\nchapter (section ) we will see\nhow to implement these and other operations for manipulating tables.\n\nHere is how data-directed programming can be used in the complex-number\nsystem.\n\nBen, who developed the rectangular representation, implements his\ncode just as he did originally.\n\nHe defines a collection of\nfunctions\nor a\npackage , and interfaces these to the rest of the system by adding\nentries to the table that tell the system how to operate on rectangular\nnumbers.\n\nThis is accomplished by calling the following\nfunction:\n\n```javascript\noperation_table_from_chapter_3\n\n// operation_table, put and get\n// from chapter 3 (section 3.3.3)\n```\n\n```javascript\ninstall_rectangular_package_usage\n\ninstall_rectangular_package();\n```\n\n```javascript\ninstall_rectangular_package\n operation_table_from_chapter_3\n operation_table\n attach_tag\n square_definition\n 'done'\n install_rectangular_package_usage\n\nfunction install_rectangular_package() {\n // internal functions\n function real_part(z) { return head(z); }\n function imag_part(z) { return tail(z); }\n function make_from_real_imag(x, y) { return pair(x, y); }\n function magnitude(z) {\n return math_sqrt(square(real_part(z)) + square(imag_part(z)));\n }\n function angle(z) {\n return math_atan2(imag_part(z), real_part(z));\n }\n function make_from_mag_ang(r, a) {\n return pair(r * math_cos(a), r * math_sin(a));\n }\n\n // interface to the rest of the system\n function tag(x) { return attach_tag(\"rectangular\", x); }\n put(\"real_part\", list(\"rectangular\"), real_part);\n put(\"imag_part\", list(\"rectangular\"), imag_part);\n put(\"magnitude\", list(\"rectangular\"), magnitude);\n put(\"angle\", list(\"rectangular\"), angle);\n put(\"make_from_real_imag\", \"rectangular\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"rectangular\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\n```\n\nNotice that the internal\nfunctions\nhere are the same\nfunctions\nfrom section that\nBen wrote when he was working in isolation.\n\nNo changes are necessary in\norder to interface them to the rest of the system.", + "token_count": 290, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", @@ -530,8 +560,8 @@ "chunk_id": "Building_Abstractions_with_Data_Data-Directed_Programming_and_Additivity_3" }, { - "content": "Moreover, since these\nfunction declarations\nare internal to the installation\nfunction,\nBen needn t worry about name conflicts with other\nfunctions\noutside the rectangular package.\n\nTo interface these to the rest of the\nsystem, Ben installs his\nreal_part\nfunction\nunder the operation name\nreal_part\nand the type\nlist(\"rectangular\"),\nand similarly for the other selectors. s internally defined\nconstructors, except that they attach the tag.\n\nAlyssa s\n\n```javascript\ninstall_polar_package\n operation_table_from_chapter_3\n operation_table\n attach_tag\n square_definition\n install_polar_package_usage\n 'done'\n\nfunction install_polar_package() {\n // internal functions\n function magnitude(z) { return head(z); }\n function angle(z) { return tail(z); }\n function make_from_mag_ang(r, a) { return pair(r, a); }\n function real_part(z) {\n return magnitude(z) * math_cos(angle(z));\n }\n function imag_part(z) {\n return magnitude(z) * math_sin(angle(z));\n }\n function make_from_real_imag(x, y) {\n return pair(math_sqrt(square(x) + square(y)),\n math_atan2(y, x));\n }\n\n // interface to the rest of the system\n function tag(x) { return attach_tag(\"polar\", x); }\n put(\"real_part\", list(\"polar\"), real_part);\n put(\"imag_part\", list(\"polar\"), imag_part);\n put(\"magnitude\", list(\"polar\"), magnitude);\n put(\"angle\", list(\"polar\"), angle);\n put(\"make_from_real_imag\", \"polar\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"polar\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\n```\n\n```javascript\ninstall_polar_package_usage\n install_polar_package\n\ninstall_polar_package();\n```\n\nEven though Ben and Alyssa both still use their original functions defined with the same names as each other s (e.g., real_part), these declarations are\n\nnow internal to different functions (see section ), so there is no name conflict.\n\nThe complex-arithmetic selectors access the table by means of a general operation function called apply_generic, which applies a generic operation to some arguments.\n\n```javascript\nThe function\n\tapply_generic\n```\n\nlooks in the table under the name of the operation and the types of the arguments and applies the resulting function if one is present:", - "token_count": 269, + "content": "No changes are necessary in\norder to interface them to the rest of the system.\n\nTo interface these to the rest of the\nsystem, Ben installs his\nreal_part\nfunction\nunder the operation name\nreal_part\nand the type\nlist(\"rectangular\"),\nand similarly for the other selectors.\n\nThe interface also defines the\nconstructors to be used by the external system.\n\nThese are identical to Ben s internally defined\nconstructors, except that they attach the tag.\n\nAlyssa s polar package is analogous:\n\n```javascript\ninstall_polar_package\n operation_table_from_chapter_3\n operation_table\n attach_tag\n square_definition\n install_polar_package_usage\n 'done'\n\nfunction install_polar_package() {\n // internal functions\n function magnitude(z) { return head(z); }\n function angle(z) { return tail(z); }\n function make_from_mag_ang(r, a) { return pair(r, a); }\n function real_part(z) {\n return magnitude(z) * math_cos(angle(z));\n }\n function imag_part(z) {\n return magnitude(z) * math_sin(angle(z));\n }\n function make_from_real_imag(x, y) {\n return pair(math_sqrt(square(x) + square(y)),\n math_atan2(y, x));\n }\n\n // interface to the rest of the system\n function tag(x) { return attach_tag(\"polar\", x); }\n put(\"real_part\", list(\"polar\"), real_part);\n put(\"imag_part\", list(\"polar\"), imag_part);\n put(\"magnitude\", list(\"polar\"), magnitude);\n put(\"angle\", list(\"polar\"), angle);\n put(\"make_from_real_imag\", \"polar\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"polar\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\n```\n\n```javascript\ninstall_polar_package_usage\n install_polar_package\n\ninstall_polar_package();\n```\n\nEven though Ben and Alyssa both still use their original functions defined with the same names as each other s (e.g., real_part), these declarations are\n\nnow internal to different functions (see section ), so there is no name conflict.\n\nThe complex-arithmetic selectors access the table by means of a general\noperation\nfunction\ncalled\napply_generic,\nwhich applies a generic operation to some arguments.\n\nThe function apply_generic\nlooks in the table under the name of the operation and the types of the\narguments and applies the resulting\nfunction\nif one is present:", + "token_count": 279, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", @@ -540,8 +570,8 @@ "chunk_id": "Building_Abstractions_with_Data_Data-Directed_Programming_and_Additivity_4" }, { - "content": "looks in the table under the name of the operation and the types of the arguments and applies the resulting function if one is present:\n\n```javascript\napply_definition\n\n// In Source, most functions have a fixed number of arguments.\n// (The function list is the only exception, to this so far.)\n// The function apply_in_underlying_javascript allows us to\n// apply any given function fun to all elements of the argument\n// list args, as if they were separate arguments\nfunction apply(fun, args) {\n return apply_in_underlying_javascript(fun, args);\n}\n```\n\n```javascript\napply_generic\n apply_definition\n\nfunction apply_generic(op, args) {\n const type_tags = map(type_tag, args);\n const fun = get(op, type_tags);\n return ! is_undefined(fun)\n ? apply_in_underlying_javascript(fun, map(contents, args))\n : error(list(op, type_tags),\n \"no method for these types -- apply_generic\");\n}\n```\n\nUsing apply_generic, we can define our generic selectors as follows:\n\n```javascript\ngeneric_selectors\n apply_generic\n generic_selectors_example\n 9\n\nfunction real_part(z) { return apply_generic(\"real_part\", list(z)); }\n\nfunction imag_part(z) { return apply_generic(\"imag_part\", list(z)); }\n\nfunction magnitude(z) { return apply_generic(\"magnitude\", list(z)); }\n\nfunction angle(z) { return apply_generic(\"angle\", list(z)); }\n```\n\n```javascript\ngeneric_selectors_example\n install_rectangular_package\n install_rectangular_package_usage\n install_polar_package\n complex_number_calculation\n generic_constructors\n\nconst my_complex_number =\n make_from_real_imag(1.0, 4.5);\n\nconst result =\n add_complex(my_complex_number,\n my_complex_number);\n\nimag_part(result);\n```\n\nObserve that these do not change at all if a new representation is added to the system.\n\nWe can also extract from the table the constructors to be used by the\nprograms external to the packages in making complex numbers from real and\nimaginary parts and from magnitudes and angles.\n\nAs in\nsection , we construct rectangular\nnumbers whenever we have real and imaginary parts, and polar numbers\nwhenever we have magnitudes and angles:\n\n```javascript\ngeneric_constructors\n generic_selectors\n generic_selectors_example\n 9\n\nfunction make_from_real_imag(x, y) {\n return get(\"make_from_real_imag\", \"rectangular\")(x, y);\n}\nfunction make_from_mag_ang(r, a) {\n return get(\"make_from_mag_ang\", \"polar\")(r, a);\n}\n```\n\nThe key idea of data-directed programming is to handle generic operations\nin programs by dealing explicitly with operation-and-type tables, such as\nthe table in\nfigure.", - "token_count": 309, + "content": "The function apply_generic\nlooks in the table under the name of the operation and the types of the\narguments and applies the resulting\nfunction\nif one is present:\n\n```javascript\napply_generic\n apply_definition\n\nfunction apply_generic(op, args) {\n const type_tags = map(type_tag, args);\n const fun = get(op, type_tags);\n return ! is_undefined(fun)\n ? apply_in_underlying_javascript(fun, map(contents, args))\n : error(list(op, type_tags),\n \"no method for these types -- apply_generic\");\n}\n```\n\nUsing apply_generic, we can define our generic selectors as follows:\n\n```javascript\ngeneric_selectors\n apply_generic\n generic_selectors_example\n 9\n\nfunction real_part(z) { return apply_generic(\"real_part\", list(z)); }\n\nfunction imag_part(z) { return apply_generic(\"imag_part\", list(z)); }\n\nfunction magnitude(z) { return apply_generic(\"magnitude\", list(z)); }\n\nfunction angle(z) { return apply_generic(\"angle\", list(z)); }\n```\n\n```javascript\ngeneric_selectors_example\n install_rectangular_package\n install_rectangular_package_usage\n install_polar_package\n complex_number_calculation\n generic_constructors\n\nconst my_complex_number =\n make_from_real_imag(1.0, 4.5);\n\nconst result =\n add_complex(my_complex_number,\n my_complex_number);\n\nimag_part(result);\n```\n\nObserve that these do not change at all if a new representation is added to the system.\n\nWe can also extract from the table the constructors to be used by the\nprograms external to the packages in making complex numbers from real and\nimaginary parts and from magnitudes and angles.\n\nAs in\nsection , we construct rectangular\nnumbers whenever we have real and imaginary parts, and polar numbers\nwhenever we have magnitudes and angles:\n\n```javascript\ngeneric_constructors\n generic_selectors\n generic_selectors_example\n 9\n\nfunction make_from_real_imag(x, y) {\n return get(\"make_from_real_imag\", \"rectangular\")(x, y);\n}\nfunction make_from_mag_ang(r, a) {\n return get(\"make_from_mag_ang\", \"polar\")(r, a);\n}\n```\n\nThe key idea of data-directed programming is to handle generic operations\nin programs by dealing explicitly with operation-and-type tables, such as\nthe table in\nfigure.\n\nThe style of programming we used in\nsection organized the required\ndispatching on type by having each operation take care of its own\ndispatching.\n\nIn effect, this decomposes the operation-and-type table into\nrows, with each generic operation\nfunction\nrepresenting a row of the table.", + "token_count": 294, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", @@ -550,9 +580,9 @@ "chunk_id": "Building_Abstractions_with_Data_Data-Directed_Programming_and_Additivity_5" }, { - "content": "The key idea of data-directed programming is to handle generic operations\nin programs by dealing explicitly with operation-and-type tables, such as\nthe table in\nfigure.\n\nThe style of programming we used in\nsection organized the required\ndispatching on type by having each operation take care of its own\ndispatching.\n\nIn effect, this decomposes the operation-and-type table into\nrows, with each generic operation\nfunction\nrepresenting a row of the table.\n\nAn alternative implementation strategy is to decompose the table into\ncolumns and, instead of using intelligent operations that\ndispatch on data types, to work with intelligent data\nobjects that dispatch on operation names.\n\nWe can do this by\narranging things so that a data object, such as a rectangular number, is\nrepresented as a\nfunction\nthat takes as input the required operation name and performs the operation\nindicated.\n\nIn such a discipline,\nmake_from_real_imag\ncould be written as", - "token_count": 146, - "has_code": false, + "content": "In effect, this decomposes the operation-and-type table into\nrows, with each generic operation\nfunction\nrepresenting a row of the table.\n\nWe can do this by\narranging things so that a data object, such as a rectangular number, is\nrepresented as a\nfunction\nthat takes as input the required operation name and performs the operation\nindicated.\n\nIn such a discipline,\nmake_from_real_imag\ncould be written as\n\n```javascript\nmake_from_real_imag_message_passing\n square_definition\n message_passing_example\n 9\n\nfunction make_from_real_imag(x, y) {\n function dispatch(op) {\n return op === \"real_part\"\n ? x\n : op === \"imag_part\"\n ? y\n : op === \"magnitude\"\n ? math_sqrt(square(x) + square(y))\n : op === \"angle\"\n ? math_atan2(y, x)\n : error(op, \"unknown op -- make_from_real_imag\");\n }\n return dispatch;\n}\n\nfunction make_from_real_imag(x, y) {\n function dispatch(op) {\n return op === \"real_part\"\n ? x\n : op === \"imag_part\"\n ? y\n : op === \"magnitude\"\n ? math_sqrt(square(x) + square(y))\n : op === \"angle\"\n ? math_atan2(y, x)\n : error(op, \"unknown op -- make_from_real_imag\");\n }\n return dispatch;\n}\n\nfunction make_from_mag_ang(r, a) {\n function dispatch(op) {\n return op === \"real_part\"\n ? r * math_cos(a)\n : op === \"imag_part\"\n ? r * math_sin(a)\n : op === \"magnitude\"\n ? r\n : op === \"angle\"\n ? a\n : error(op, \"unknown op -- make_from_real_imag\");\n }\n return dispatch;\n}\n\nfunction apply_generic(op, arg) {\n return head(arg)(op);\n}\nfunction real_part(z) {\n return apply_generic(\"real_part\", list(z));\n}\nfunction imag_part(z) {\n return apply_generic(\"imag_part\", list(z));\n}\nfunction magnitude(z) {\n return apply_generic(\"magnitude\", list(z));\n}\nfunction angle(z) {\n return apply_generic(\"angle\", list(z));\n}\nfunction add_complex(z1, z2) {\n return make_from_real_imag(\n real_part(z1) + real_part(z2),\n imag_part(z1) + imag_part(z2));\n}\nfunction sub_complex(z1, z2) {\n return make_from_real_imag(\n real_part(z1) - real_part(z2),\n imag_part(z1) - imag_part(z2));\n}\nfunction mul_complex(z1, z2) {\n return make_from_mag_ang(\n magnitude(z1) * magnitude(z2),\n angle(z1) + angle(z2));\n}\nfunction div_complex(z1, z2) {\n return make_from_mag_ang(\n magnitude(z1) / magnitude(z2),\n angle(z1) - angle(z2));\n}\n// operation_table, put and get\n// from chapter 3 (section 3.3.3)\nfunction assoc(key, records) {\n return is_null(records)\n ? undefined\n : equal(key, head(head(records)))\n ? head(records)\n : assoc(key, tail(records));\n}\nfunction make_table() {\n const local_table = list(\"*table*\");\n function lookup(key_1, key_2) {\n const subtable = assoc(key_1, tail(local_table));\n if (is_undefined(subtable)) {\n return undefined;\n } else {\n const record = assoc(key_2, tail(subtable));\n return is_undefined(record)\n ? undefined\n : tail(record);\n }\n }\n function insert(key_1, key_2, value) {\n const subtable = assoc(key_1, tail(local_table));\n if (is_undefined(subtable)) {\n set_tail(local_table,\n pair(list(key_1, pair(key_2, value)),\n tail(local_table)));\n } else {\n const record = assoc(key_2, tail(subtable));\n if (is_undefined(record)) {\n \t set_tail(subtable,\n\t pair(pair(key_2, value),\n tail(subtable)));\n\t } else {\n set_tail(record, value);\n }\n }\n }\n function dispatch(m) {\n return m === \"lookup\"\n ? lookup\n : m === \"insert\"\n ? insert\n : \"undefined operation -- table\";\n }\n return dispatch;\n}\nconst operation_table = make_table();\nconst get = operation_table(\"lookup\");\nconst put = operation_table(\"insert\");\nfunction attach_tag(type_tag, contents) {\n return pair(type_tag, contents);\n}\nfunction type_tag(datum) {\n return is_pair(datum)\n ? head(datum)\n : error(datum, \"bad tagged datum -- type_tag\");\n}\nfunction contents(datum) {\n return is_pair(datum)\n ? tail(datum)\n : error(datum, \"bad tagged datum -- contents\");\n}\nfunction install_rectangular_package() {\n function real_part(z) { return head(z); }\n function imag_part(z) { return tail(z); }\n function make_from_real_imag(x, y) { return pair(x, y); }\n function magnitude(z) {\n return math_sqrt(square(real_part(z)) +\n square(imag_part(z)));\n }\n function angle(z) {\n return math_atan2(imag_part(z), real_part(z));\n }\n function make_from_mag_ang(r, a) {\n return pair(r * math_cos(a), r * math_sin(a));\n }\n // interface to the rest of the system\n function tag(x) {\n return attach_tag(\"rectangular\", x);\n }\n put(\"real_part\", list(\"rectangular\"), real_part);\n put(\"imag_part\", list(\"rectangular\"), imag_part);\n put(\"magnitude\", list(\"rectangular\"), magnitude);\n put(\"angle\", list(\"rectangular\"), angle);\n put(\"make_from_real_imag\", \"rectangular\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"rectangular\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\n\ninstall_rectangular_package();\nfunction install_polar_package() {\n // internal functions\n function magnitude(z) { return head(z); }\n function angle(z) { return tail(z); }\n function make_from_mag_ang(r, a) { return pair(r, a); }\n function real_part(z) {\n return magnitude(z) * math_cos(angle(z));\n }\n function imag_part(z) {\n return magnitude(z) * math_sin(angle(z));\n }\n function make_from_real_imag(x, y) {\n return pair(math_sqrt(square(x) + square(y)),\n math_atan2(y, x));\n }\n\n // interface to the rest of the system\n function tag(x) { return attach_tag(\"polar\", x); }\n put(\"real_part\", list(\"polar\"), real_part);\n put(\"imag_part\", list(\"polar\"), imag_part);\n put(\"magnitude\", list(\"polar\"), magnitude);\n put(\"angle\", list(\"polar\"), angle);\n put(\"make_from_real_imag\", \"polar\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"polar\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\n\ninstall_polar_package();\n```", + "token_count": 663, + "has_code": true, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", "subsection": "Data-Directed Programming and Additivity", @@ -560,8 +590,8 @@ "chunk_id": "Building_Abstractions_with_Data_Data-Directed_Programming_and_Additivity_6" }, { - "content": "In such a discipline,\nmake_from_real_imag\ncould be written as\n\n```javascript\nmake_from_real_imag_message_passing\n square_definition\n message_passing_example\n 9\n\nfunction make_from_real_imag(x, y) {\n function dispatch(op) {\n return op === \"real_part\"\n ? x\n : op === \"imag_part\"\n ? y\n : op === \"magnitude\"\n ? math_sqrt(square(x) + square(y))\n : op === \"angle\"\n ? math_atan2(y, x)\n : error(op, \"unknown op -- make_from_real_imag\");\n }\n return dispatch;\n}\n\nfunction make_from_real_imag(x, y) {\n function dispatch(op) {\n return op === \"real_part\"\n ? x\n : op === \"imag_part\"\n ? y\n : op === \"magnitude\"\n ? math_sqrt(square(x) + square(y))\n : op === \"angle\"\n ? math_atan2(y, x)\n : error(op, \"unknown op -- make_from_real_imag\");\n }\n return dispatch;\n}\n\nfunction make_from_mag_ang(r, a) {\n function dispatch(op) {\n return op === \"real_part\"\n ? r * math_cos(a)\n : op === \"imag_part\"\n ? r * math_sin(a)\n : op === \"magnitude\"\n ? r\n : op === \"angle\"\n ? a\n : error(op, \"unknown op -- make_from_real_imag\");\n }\n return dispatch;\n}\n\nfunction apply_generic(op, arg) {\n return head(arg)(op);\n}\nfunction real_part(z) {\n return apply_generic(\"real_part\", list(z));\n}\nfunction imag_part(z) {\n return apply_generic(\"imag_part\", list(z));\n}\nfunction magnitude(z) {\n return apply_generic(\"magnitude\", list(z));\n}\nfunction angle(z) {\n return apply_generic(\"angle\", list(z));\n}\nfunction add_complex(z1, z2) {\n return make_from_real_imag(\n real_part(z1) + real_part(z2),\n imag_part(z1) + imag_part(z2));\n}\nfunction sub_complex(z1, z2) {\n return make_from_real_imag(\n real_part(z1) - real_part(z2),\n imag_part(z1) - imag_part(z2));\n}\nfunction mul_complex(z1, z2) {\n return make_from_mag_ang(\n magnitude(z1) * magnitude(z2),\n angle(z1) + angle(z2));\n}\nfunction div_complex(z1, z2) {\n return make_from_mag_ang(\n magnitude(z1) / magnitude(z2),\n angle(z1) - angle(z2));\n}\n// operation_table, put and get\n// from chapter 3 (section 3.3.3)\nfunction assoc(key, records) {\n return is_null(records)\n ? undefined\n : equal(key, head(head(records)))\n ? head(records)\n : assoc(key, tail(records));\n}\nfunction make_table() {\n const local_table = list(\"*table*\");\n function lookup(key_1, key_2) {\n const subtable = assoc(key_1, tail(local_table));\n if (is_undefined(subtable)) {\n return undefined;\n } else {\n const record = assoc(key_2, tail(subtable));\n return is_undefined(record)\n ? undefined\n : tail(record);\n }\n }\n function insert(key_1, key_2, value) {\n const subtable = assoc(key_1, tail(local_table));\n if (is_undefined(subtable)) {\n set_tail(local_table,\n pair(list(key_1, pair(key_2, value)),\n tail(local_table)));\n } else {\n const record = assoc(key_2, tail(subtable));\n if (is_undefined(record)) {\n \t set_tail(subtable,\n\t pair(pair(key_2, value),\n tail(subtable)));\n\t } else {\n set_tail(record, value);\n }\n }\n }\n function dispatch(m) {\n return m === \"lookup\"\n ? lookup\n : m === \"insert\"\n ? insert\n : \"undefined operation -- table\";\n }\n return dispatch;\n}\nconst operation_table = make_table();\nconst get = operation_table(\"lookup\");\nconst put = operation_table(\"insert\");\nfunction attach_tag(type_tag, contents) {\n return pair(type_tag, contents);\n}\nfunction type_tag(datum) {\n return is_pair(datum)\n ? head(datum)\n : error(datum, \"bad tagged datum -- type_tag\");\n}\nfunction contents(datum) {\n return is_pair(datum)\n ? tail(datum)\n : error(datum, \"bad tagged datum -- contents\");\n}\nfunction install_rectangular_package() {\n function real_part(z) { return head(z); }\n function imag_part(z) { return tail(z); }\n function make_from_real_imag(x, y) { return pair(x, y); }\n function magnitude(z) {\n return math_sqrt(square(real_part(z)) +\n square(imag_part(z)));\n }\n function angle(z) {\n return math_atan2(imag_part(z), real_part(z));\n }\n function make_from_mag_ang(r, a) {\n return pair(r * math_cos(a), r * math_sin(a));\n }\n // interface to the rest of the system\n function tag(x) {\n return attach_tag(\"rectangular\", x);\n }\n put(\"real_part\", list(\"rectangular\"), real_part);\n put(\"imag_part\", list(\"rectangular\"), imag_part);\n put(\"magnitude\", list(\"rectangular\"), magnitude);\n put(\"angle\", list(\"rectangular\"), angle);\n put(\"make_from_real_imag\", \"rectangular\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"rectangular\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\n\ninstall_rectangular_package();\nfunction install_polar_package() {\n // internal functions\n function magnitude(z) { return head(z); }\n function angle(z) { return tail(z); }\n function make_from_mag_ang(r, a) { return pair(r, a); }\n function real_part(z) {\n return magnitude(z) * math_cos(angle(z));\n }\n function imag_part(z) {\n return magnitude(z) * math_sin(angle(z));\n }\n function make_from_real_imag(x, y) {\n return pair(math_sqrt(square(x) + square(y)),\n math_atan2(y, x));\n }\n\n // interface to the rest of the system\n function tag(x) { return attach_tag(\"polar\", x); }\n put(\"real_part\", list(\"polar\"), real_part);\n put(\"imag_part\", list(\"polar\"), imag_part);\n put(\"magnitude\", list(\"polar\"), magnitude);\n put(\"angle\", list(\"polar\"), angle);\n put(\"make_from_real_imag\", \"polar\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"polar\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\n\ninstall_polar_package();\n```", - "token_count": 608, + "content": "In such a discipline,\nmake_from_real_imag\ncould be written as\n\nthe object do the work:\n\n```javascript\napply_generic_message_passing\n make_from_real_imag_message_passing\n message_passing_example\n\nfunction apply_generic(op, arg) { return head(arg)(op); }\n```\n\n```javascript\nmessage_passing_example\n\nconst my_complex_number =\n make_from_real_imag(1.0, 4.5);\n\nconst result =\n add_complex(my_complex_number,\n my_complex_number);\n\nimag_part(result);\n```\n\n```javascript\ngeneric_selectors_message_passing\n apply_generic_message_passing\n generic_selectors_example\n\nfunction real_part(z) {\n return apply_generic(\"real_part\", list(z));\n}\nfunction imag_part(z) {\n return apply_generic(\"imag_part\", list(z));\n}\nfunction magnitude(z) {\n return apply_generic(\"magnitude\", list(z));\n}\nfunction angle(z) {\n return apply_generic(\"angle\", list(z));\n}\n```\n\nNote that the value returned by\nmake_from_real_imag\nis a\nfunctionthe internal\nfunction.\n\nThis is the\nfunction\nthat is invoked when\napply_generic\nrequests an operation to be performed.\n\nThis style of programming is called message passing.\n\nThe name\ncomes from the image that a data object is an entity that receives the\nrequested operation name as a message.\n\nWe have already seen\nan example of message passing in section ,\nwhere we saw how\npair,\nhead,\nand\ntail\ncould be defined with no data objects but only\nfunctions.\n\nHere we see that message passing is not a mathematical trick but a useful\ntechnique for organizing systems with generic operations.\n\nIn the remainder\nof this chapter we will continue to use data-directed programming, rather\nthan message passing, to discuss generic arithmetic operations.\n\nIn\nchapter we will return to message passing, and we will see that\nit can be a powerful tool for structuring simulation programs.", + "token_count": 226, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", @@ -570,18 +600,8 @@ "chunk_id": "Building_Abstractions_with_Data_Data-Directed_Programming_and_Additivity_7" }, { - "content": "In such a discipline,\nmake_from_real_imag\ncould be written as\n\nThe corresponding apply_generic function, which applies a generic operation to an argument, now simply feeds the operation s name to the data object and lets\n\nthe object do the work:\n\n```javascript\napply_generic_message_passing\n make_from_real_imag_message_passing\n message_passing_example\n\nfunction apply_generic(op, arg) { return head(arg)(op); }\n```\n\n```javascript\nmessage_passing_example\n\nconst my_complex_number =\n make_from_real_imag(1.0, 4.5);\n\nconst result =\n add_complex(my_complex_number,\n my_complex_number);\n\nimag_part(result);\n```\n\n```javascript\ngeneric_selectors_message_passing\n apply_generic_message_passing\n generic_selectors_example\n\nfunction real_part(z) {\n return apply_generic(\"real_part\", list(z));\n}\nfunction imag_part(z) {\n return apply_generic(\"imag_part\", list(z));\n}\nfunction magnitude(z) {\n return apply_generic(\"magnitude\", list(z));\n}\nfunction angle(z) {\n return apply_generic(\"angle\", list(z));\n}\n```\n\nNote that the value returned by\nmake_from_real_imag\nis a\nfunctionthe internal\nfunction.\n\nThis is the\nfunction\nthat is invoked when\napply_generic\nrequests an operation to be performed.\n\nThis style of programming is called message passing.\n\nThe name\ncomes from the image that a data object is an entity that receives the\nrequested operation name as a message.\n\nWe have already seen\nan example of message passing in section ,\nwhere we saw how\npair,\nhead,\nand\ntail\ncould be defined with no data objects but only\nfunctions.\n\nHere we see that message passing is not a mathematical trick but a useful\ntechnique for organizing systems with generic operations.\n\nIn the remainder\nof this chapter we will continue to use data-directed programming, rather\nthan message passing, to discuss generic arithmetic operations.\n\nIn\nchapter we will return to message passing, and we will see that\nit can be a powerful tool for structuring simulation programs.", - "token_count": 251, - "has_code": true, - "chapter": "Building Abstractions with Data", - "section": "Multiple Representations for Abstract Data", - "subsection": "Data-Directed Programming and Additivity", - "chunk_index": 8, - "chunk_id": "Building_Abstractions_with_Data_Data-Directed_Programming_and_Additivity_8" - }, - { - "content": "We will develop a system that performs arithmetic operations on complex\nnumbers as a simple but unrealistic example of a program that uses generic\noperations.\n\nWe begin by discussing two plausible representations for\ncomplex numbers as ordered pairs: rectangular form (real part and imaginary\npart) and polar form (magnitude and angle).\nwill show how both representations can be made to coexist in a single\nsystem through the use of type tags and generic operations.\n\nLike rational numbers, complex numbers are naturally represented as ordered\npairs.\n\nThe set of complex numbers can be thought of as a two-dimensional\nspace with two orthogonal axes, the real axis and the\nimaginary axis.\n\n(See\nfigure.) From this point of view,\nthe complex number $z=x+iy$ (where\n$i^{2} =-1$ ) can be thought of as the point in\nthe plane whose real coordinate is $x$ and whose\nimaginary coordinate is $y$.\n\nAddition of complex\nnumbers reduces in this representation to addition of coordinates:\n\\[\n\\begin{array}{lll}\n\\mbox{Real-part}(z_{1}+z_{2}) & = &\n\\mbox{Real-part}(z_{1})+\\mbox{Real-part}(z_{2}) \\\\[1ex]\n\\mbox{Imaginary-part}(z_{1} +z_{2}) & = &\n\\mbox{Imaginary-part}(z_1)+\\mbox{Imaginary-part}(z_2)\n\\end{array}\n\\]\n\nWhen multiplying complex numbers, it is more natural to think in terms\nof representing a complex number in polar form, as a magnitude and an\nangle ( $r$ and $A$\nin figure ).\n\nThe product of two\ncomplex numbers is the vector obtained by stretching one complex number by\n\\[\n\\begin{array}{lll}\n\\mbox{Magnitude}(z_{1}\\cdot z_{2}) & = &\n\\mbox{Magnitude}(z_{1})\\cdot\\mbox{Magnitude}(z_{2})\\\\[1ex]\n\\mbox{Angle}(z_{1}\\cdot z_{2}) & = &\n\\mbox{Angle}(z_{1})+\\mbox{Angle}(z_{2})\n\\end{array}\n\\]\n\nThus, there are two different representations for complex numbers,\nwhich are appropriate for different operations.\n\nYet, from the\nviewpoint of someone writing a program that uses complex numbers, the\nprinciple of data abstraction suggests that all the operations for\nmanipulating complex numbers should be available regardless of which\nrepresentation is used by the computer.", - "token_count": 292, + "content": "We will develop a system that performs arithmetic operations on complex\nnumbers as a simple but unrealistic example of a program that uses generic\noperations.\n\nWe begin by discussing two plausible representations for\ncomplex numbers as ordered pairs: rectangular form (real part and imaginary\npart) and polar form (magnitude and angle).\n\nSection\nwill show how both representations can be made to coexist in a single\nsystem through the use of type tags and generic operations.\n\nLike rational numbers, complex numbers are naturally represented as ordered\npairs.\n\nThe set of complex numbers can be thought of as a two-dimensional\nspace with two orthogonal axes, the real axis and the\nimaginary axis.\n\n(See\nfigure.) From this point of view,\nthe complex number $z=x+iy$ (where\n$i^{2} =-1$ ) can be thought of as the point in\nthe plane whose real coordinate is $x$ and whose\nimaginary coordinate is $y$.\n\nAddition of complex\nnumbers reduces in this representation to addition of coordinates:\n\\[\n\\begin{array}{lll}\n\\mbox{Real-part}(z_{1}+z_{2}) & = &\n\\mbox{Real-part}(z_{1})+\\mbox{Real-part}(z_{2}) \\\\[1ex]\n\\mbox{Imaginary-part}(z_{1} +z_{2}) & = &\n\\mbox{Imaginary-part}(z_1)+\\mbox{Imaginary-part}(z_2)\n\\end{array}\n\\]\n\nWhen multiplying complex numbers, it is more natural to think in terms\nof representing a complex number in polar form, as a magnitude and an\nangle ( $r$ and $A$\nin figure ).\n\nThe product of two\ncomplex numbers is the vector obtained by stretching one complex number by\nthe length of the other and then rotating it through the angle of the other:\n\\[\n\\begin{array}{lll}\n\\mbox{Magnitude}(z_{1}\\cdot z_{2}) & = &\n\\mbox{Magnitude}(z_{1})\\cdot\\mbox{Magnitude}(z_{2})\\\\[1ex]\n\\mbox{Angle}(z_{1}\\cdot z_{2}) & = &\n\\mbox{Angle}(z_{1})+\\mbox{Angle}(z_{2})\n\\end{array}\n\\]\n\nThus, there are two different representations for complex numbers,\nwhich are appropriate for different operations.", + "token_count": 269, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", @@ -590,8 +610,8 @@ "chunk_id": "Building_Abstractions_with_Data_Representations_for_Complex_Numbers_1" }, { - "content": "Yet, from the\nviewpoint of someone writing a program that uses complex numbers, the\nprinciple of data abstraction suggests that all the operations for\nmanipulating complex numbers should be available regardless of which\nrepresentation is used by the computer.\n\nFor example, it is often\nuseful to be able to find the magnitude of a complex number that is\nspecified by rectangular coordinates.\n\nSimilarly, it is often useful\nto be able to determine the real part of a complex number that is\nspecified by polar coordinates.\n\nComplex numbers as points in the plane.\n\nTo design such a system, we can follow the same.\n\nAssume that the\noperations on complex numbers are implemented in terms of four selectors:\nreal_part,\nimag_part,\nmagnitude,\nand\nfunctions\nfor constructing complex numbers:\nmake_from_real_imag\nreturns a complex number with specified real and imaginary parts, and\nmake_from_mag_ang\nreturns a complex number with specified magnitude and angle.\n\nThese\nfunctions\nhave the property that, for any complex number\n\n```javascript\nmake_from_real_imag(real_part(z), imag_part(z));\n```\n\nand\n\n```javascript\nmake_from_mag_ang(magnitude(z), angle(z));\n```\n\nproduce complex numbers that are equal to\n\nUsing these constructors and selectors, we can implement arithmetic on\ncomplex numbers using the abstract data specified by the\nconstructors and selectors, just as we did for rational numbers in\nsection.\n\nAs shown in the formulas\nabove, we can add and subtract complex numbers in terms of real and\nimaginary parts while multiplying and dividing complex numbers in terms of\nmagnitudes and angles:\n\n```javascript\ncomplex_number_calculation\n\nfunction add_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) + real_part(z2),\n imag_part(z1) + imag_part(z2));\n}\nfunction sub_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) - real_part(z2),\n imag_part(z1) - imag_part(z2));\n}\nfunction mul_complex(z1, z2) {\n return make_from_mag_ang(magnitude(z1) * magnitude(z2),\n angle(z1) + angle(z2));\n}\nfunction div_complex(z1, z2) {\n return make_from_mag_ang(magnitude(z1) / magnitude(z2),\n angle(z1) - angle(z2));\n}\n```", - "token_count": 287, + "content": "Thus, there are two different representations for complex numbers,\nwhich are appropriate for different operations.\n\nFor example, it is often\nuseful to be able to find the magnitude of a complex number that is\nspecified by rectangular coordinates.\n\nSimilarly, it is often useful\nto be able to determine the real part of a complex number that is\nspecified by polar coordinates.\n\nComplex numbers as points in the plane.\n\nTo design such a system, we can follow the same\ndata-abstraction strategy we followed in designing the rational-number\npackage in section.\n\nAssume that the\noperations on complex numbers are implemented in terms of four selectors:\nreal_part,\nimag_part,\nmagnitude,\nand.\n\nAlso assume that we have two\nfunctions\nfor constructing complex numbers:\nmake_from_real_imag\nreturns a complex number with specified real and imaginary parts, and\nmake_from_mag_ang\nreturns a complex number with specified magnitude and angle.\n\nThese\nfunctions\nhave the property that, for any complex number\n, both\n\n```javascript\nmake_from_real_imag(real_part(z), imag_part(z));\n```\n\nand\n\n```javascript\nmake_from_mag_ang(magnitude(z), angle(z));\n```\n\nproduce complex numbers that are equal to.\n\nUsing these constructors and selectors, we can implement arithmetic on\ncomplex numbers using the abstract data specified by the\nconstructors and selectors, just as we did for rational numbers in\nsection.\n\nAs shown in the formulas\nabove, we can add and subtract complex numbers in terms of real and\nimaginary parts while multiplying and dividing complex numbers in terms of\nmagnitudes and angles:\n\n```javascript\ncomplex_number_calculation\n\nfunction add_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) + real_part(z2),\n imag_part(z1) + imag_part(z2));\n}\nfunction sub_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) - real_part(z2),\n imag_part(z1) - imag_part(z2));\n}\nfunction mul_complex(z1, z2) {\n return make_from_mag_ang(magnitude(z1) * magnitude(z2),\n angle(z1) + angle(z2));\n}\nfunction div_complex(z1, z2) {\n return make_from_mag_ang(magnitude(z1) / magnitude(z2),\n angle(z1) - angle(z2));\n}\n```", + "token_count": 282, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", @@ -600,8 +620,8 @@ "chunk_id": "Building_Abstractions_with_Data_Representations_for_Complex_Numbers_2" }, { - "content": "As shown in the formulas\nabove, we can add and subtract complex numbers in terms of real and\nimaginary parts while multiplying and dividing complex numbers in terms of\nmagnitudes and angles:\n\nTo complete the complex-number package, we must choose a representation and\nwe must implement the constructors and selectors in terms of primitive\nnumbers and primitive list structure.\n\nThere are two obvious ways to do\nthis: We can represent a complex number in rectangular form\nas a pair (real part, imaginary part) or in polar form as a\npair (magnitude, angle).\n\nWhich shall we choose?\n\nIn order to make the different choices concrete, imagine that there are two\nprogrammers, Ben Bitdiddle and Alyssa P.\n\nHacker, who are independently\ndesigning representations for the complex-number system.\n\nBen chooses to represent\n\\[\n\\begin{array}{lllllll}\nx & = & r\\ \\cos A & \\quad \\quad \\quad & r & = & \\sqrt{x^2 +y^2} \\\\\ny & = & r\\ \\sin A & & A &= & \\arctan (y,x)\n\\end{array}\n\\]\nwhich relate the real and imaginary parts ( $x$ ,\n$y$ ) to the magnitude and the angle\n$(r, A)$. s representation is therefore given by the following selectors\nand constructors:\n\n```javascript\nmake_complex_number1\n complex_number_calculation\n square_definition\n make_complex_number_example\n -3\n\nfunction real_part(z) { return head(z); }\n\nfunction imag_part(z) { return tail(z); }\n\nfunction magnitude(z) {\n return math_sqrt(square(real_part(z)) + square(imag_part(z)));\n}\nfunction angle(z) {\n return math_atan2(imag_part(z), real_part(z));\n}\nfunction make_from_real_imag(x, y) { return pair(x, y); }\n\nfunction make_from_mag_ang(r, a) {\n return pair(r * math_cos(a), r * math_sin(a));\n}\n```\n\n```javascript\nmake_complex_number_example\n\nconst my_co_num_1 = make_from_real_imag(2.5, -0.5);\nconst my_co_num_2 = make_from_real_imag(2.5, -0.5);\n\nconst result = add_complex(my_co_num_1,\n mul_complex(my_co_num_2,\n my_co_num_2));\n\nimag_part(result);\n```\n\nAlyssa, in contrast, chooses to represent complex numbers in For her, selecting the magnitude and angle is straightforward, but she has to use the s\n\nrepresentation is:", - "token_count": 298, + "content": "As shown in the formulas\nabove, we can add and subtract complex numbers in terms of real and\nimaginary parts while multiplying and dividing complex numbers in terms of\nmagnitudes and angles:\n\nThere are two obvious ways to do\nthis: We can represent a complex number in rectangular form\nas a pair (real part, imaginary part) or in polar form as a\npair (magnitude, angle).\n\nWhich shall we choose?\n\nIn order to make the different choices concrete, imagine that there are two\nprogrammers, Ben Bitdiddle and Alyssa P.\n\nHacker, who are independently\ndesigning representations for the complex-number system.\n\nBen chooses to represent\ncomplex numbers in rectangular form.\n\nWith this\nchoice, selecting the real and imaginary parts of a complex number is\nstraightforward, as is constructing a complex number with given real and\nimaginary parts.\n\nTo find the magnitude and the angle, or to construct a\ncomplex number with a given magnitude and angle, he uses the trigonometric\nrelations\n\\[\n\\begin{array}{lllllll}\nx & = & r\\ \\cos A & \\quad \\quad \\quad & r & = & \\sqrt{x^2 +y^2} \\\\\ny & = & r\\ \\sin A & & A &= & \\arctan (y,x)\n\\end{array}\n\\]\nwhich relate the real and imaginary parts ( $x$ ,\n$y$ ) to the magnitude and the angle\n$(r, A)$.\n\nBen s representation is therefore given by the following selectors\nand constructors:\n\n```javascript\nmake_complex_number1\n complex_number_calculation\n square_definition\n make_complex_number_example\n -3\n\nfunction real_part(z) { return head(z); }\n\nfunction imag_part(z) { return tail(z); }\n\nfunction magnitude(z) {\n return math_sqrt(square(real_part(z)) + square(imag_part(z)));\n}\nfunction angle(z) {\n return math_atan2(imag_part(z), real_part(z));\n}\nfunction make_from_real_imag(x, y) { return pair(x, y); }\n\nfunction make_from_mag_ang(r, a) {\n return pair(r * math_cos(a), r * math_sin(a));\n}\n```\n\n```javascript\nmake_complex_number_example\n\nconst my_co_num_1 = make_from_real_imag(2.5, -0.5);\nconst my_co_num_2 = make_from_real_imag(2.5, -0.5);\n\nconst result = add_complex(my_co_num_1,\n mul_complex(my_co_num_2,\n my_co_num_2));\n\nimag_part(result);\n```", + "token_count": 301, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", @@ -610,8 +630,8 @@ "chunk_id": "Building_Abstractions_with_Data_Representations_for_Complex_Numbers_3" }, { - "content": "representation is:\n\n```javascript\nmake_complex_number2\n complex_number_calculation\n square_definition\n make_complex_number_example\n -3\n\nfunction real_part(z) {\n return magnitude(z) * math_cos(angle(z));\n}\nfunction imag_part(z) {\n return magnitude(z) * math_sin(angle(z));\n}\nfunction magnitude(z) { return head(z); }\n\nfunction angle(z) { return tail(z); }\n\nfunction make_from_real_imag(x, y) {\n return pair(math_sqrt(square(x) + square(y)),\n math_atan2(y, x));\n}\nfunction make_from_mag_ang(r, a) { return pair(r, a); }\n```\n\nThe discipline of data abstraction ensures that the same implementation of add_@complex, sub_complex, mul_complex, and div_complex will work with either Ben s representation or Alyssa\n\ns representation.", - "token_count": 83, + "content": "Ben s representation is therefore given by the following selectors\nand constructors:\n\nFor her, selecting the magnitude and angle is straightforward, but she has\nto use the\ntrigonometric relations to obtain the real and imaginary parts.\n\nAlyssa s representation is:\n\n```javascript\nmake_complex_number2\n complex_number_calculation\n square_definition\n make_complex_number_example\n -3\n\nfunction real_part(z) {\n return magnitude(z) * math_cos(angle(z));\n}\nfunction imag_part(z) {\n return magnitude(z) * math_sin(angle(z));\n}\nfunction magnitude(z) { return head(z); }\n\nfunction angle(z) { return tail(z); }\n\nfunction make_from_real_imag(x, y) {\n return pair(math_sqrt(square(x) + square(y)),\n math_atan2(y, x));\n}\nfunction make_from_mag_ang(r, a) { return pair(r, a); }\n```\n\nThe discipline of data abstraction ensures that the same implementation of add_@complex, sub_complex, mul_complex, and div_complex will work with either Ben s representation or Alyssa\n\ns representation.", + "token_count": 121, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", @@ -620,8 +640,8 @@ "chunk_id": "Building_Abstractions_with_Data_Representations_for_Complex_Numbers_4" }, { - "content": "One way to view data abstraction is as an application of the\nprinciple of least commitment.\n\nIn implementing the\ncomplex-number system in\nsection , we can\nuse either Ben s rectangular representation or Alyssa s polar\nrepresentation.\n\nThe abstraction barrier formed by the selectors and\nconstructors permits us to defer to the last possible moment the choice of\na concrete representation for our data objects and thus retain maximum\nflexibility in our system design.\n\nThe principle of least commitment can be carried to even further extremes.\n\nIf we desire, we can maintain the ambiguity of representation even\nafter we have designed the selectors and constructors, and elect\nto use both Ben s representation and Alyssa s\nrepresentation.\n\nIf both representations are included in a single system,\nhowever, we will need some way to distinguish data in polar form from data\nin rectangular form.\n\nOtherwise, if we were asked, for instance, to find\nthe $(3,4)$ , we wouldn t know whether to\nanswer 5 (interpreting the number in rectangular form) or\n3 (interpreting the number in polar form).\n\nA straightforward way to\naccomplish this distinction is to include a\ntype tag the\nstring \"rectangular\"\nor\n\"polar\"as\npart of each complex number.\n\nThen when we need to manipulate a complex\nnumber we can use the tag to decide which selector to apply.\n\nIn order to manipulate tagged data, we will assume that we have\nfunctions\ntype_tag\nand\nfunction\nattach_tag\nthat takes a tag and contents and produces a tagged data object.\n\nA\nstraightforward way to implement this is to use ordinary list structure:", - "token_count": 261, + "content": "One way to view data abstraction is as an application of the\nprinciple of least commitment.\n\nIn implementing the\ncomplex-number system in\nsection , we can\nuse either Ben s rectangular representation or Alyssa s polar\nrepresentation.\n\nThe abstraction barrier formed by the selectors and\nconstructors permits us to defer to the last possible moment the choice of\na concrete representation for our data objects and thus retain maximum\nflexibility in our system design.\n\nThe principle of least commitment can be carried to even further extremes.\n\nIf we desire, we can maintain the ambiguity of representation even\nafter we have designed the selectors and constructors, and elect\nto use both Ben s representation and Alyssa s\nrepresentation.\n\nIf both representations are included in a single system,\nhowever, we will need some way to distinguish data in polar form from data\nin rectangular form.\n\nOtherwise, if we were asked, for instance, to find\nthe of the pair\n$(3,4)$ , we wouldn t know whether to\nanswer 5 (interpreting the number in rectangular form) or\n3 (interpreting the number in polar form).\n\nA straightforward way to\naccomplish this distinction is to include a\ntype tag the\nstring \"rectangular\"\nor\n\"polar\"as\npart of each complex number.\n\nThen when we need to manipulate a complex\nnumber we can use the tag to decide which selector to apply.\n\nIn order to manipulate tagged data, we will assume that we have\nfunctions\ntype_tag\nand that extract from a data object\nthe tag and the actual contents (the polar or rectangular coordinates, in\nthe case of a complex number).\n\nWe will also postulate a\nfunction\nattach_tag\nthat takes a tag and contents and produces a tagged data object.\n\nA\nstraightforward way to implement this is to use ordinary list structure:", + "token_count": 293, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", @@ -630,8 +650,8 @@ "chunk_id": "Building_Abstractions_with_Data_Tagged_data_1" }, { - "content": "A\nstraightforward way to implement this is to use ordinary list structure:\n\n```javascript\nattach_tag\n attach_tag_example\n 'frequency_list'\n\nfunction attach_tag(type_tag, contents) {\n return pair(type_tag, contents);\n}\nfunction type_tag(datum) {\n return is_pair(datum)\n ? head(datum)\n : error(datum, \"bad tagged datum -- type_tag\");\n}\nfunction contents(datum) {\n return is_pair(datum)\n ? tail(datum)\n : error(datum, \"bad tagged datum -- contents\");\n}\n```\n\n```javascript\nattach_tag_example\n\nconst f_1 = list(\"A\", 4);\nconst my_frequency_1 =\n attach_tag(\"frequency_list\", f_1);\n\ntype_tag(my_frequency_1);\n```\n\n```javascript\nUsing\n\ttype_tag,\n```\n\nwe can define predicates is_rectangular and is_polar, which recognize rectangular and polar numbers, respectively:\n\n```javascript\nrectangular_or_polar\n rectangular_or_polar_example\n attach_tag\n\nfunction is_rectangular(z) {\n return type_tag(z) === \"rectangular\";\n}\nfunction is_polar(z) {\n return type_tag(z) === \"polar\";\n}\n```\n\nWith type tags, Ben and Alyssa can now modify their code so that their two\ndifferent representations can coexist in the same system.\n\nWhenever Ben\nconstructs a complex number, he tags it as rectangular.\n\nWhenever Alyssa\nconstructs a complex number, she tags it as polar.\n\nIn addition, Ben and\nAlyssa must make sure that the names of their\nfunctions\ndo not conflict.\n\nOne way to do this is for Ben to append the suffix\nfunctions\nand for Alyssa to append s revised rectangular representation from\nsection :\n\n```javascript\nmake_complex_number_rectangular\n attach_tag\n square_definition\n make_complex_number_rectangular_example\n 1.932653061713073\n\nfunction real_part_rectangular(z) { return head(z); }\n\nfunction imag_part_rectangular(z) { return tail(z); }\n\nfunction magnitude_rectangular(z) {\n return math_sqrt(square(real_part_rectangular(z)) +\n square(imag_part_rectangular(z)));\n}\nfunction angle_rectangular(z) {\n return math_atan2(imag_part_rectangular(z),\n real_part_rectangular(z));\n}\nfunction make_from_real_imag_rectangular(x, y) {\n return attach_tag(\"rectangular\", pair(x, y));\n}\nfunction make_from_mag_ang_rectangular(r, a) {\n return attach_tag(\"rectangular\",\n pair(r * math_cos(a), r * math_sin(a)));\n}\n```\n\n```javascript\nmake_complex_number_rectangular_example\n\nconst bens_co_num = make_from_mag_ang_rectangular(\n 3.0, 0.7);\n\nimag_part_rectangular(contents(bens_co_num));\n```\n\nand here is Alyssa s revised polar representation:", - "token_count": 269, + "content": "A\nstraightforward way to implement this is to use ordinary list structure:\n\n```javascript\nattach_tag_example\n\nconst f_1 = list(\"A\", 4);\nconst my_frequency_1 =\n attach_tag(\"frequency_list\", f_1);\n\ntype_tag(my_frequency_1);\n```\n\nUsing type_tag, we can define predicates is_rectangular and is_polar, which recognize rectangular and polar numbers, respectively:\n\n```javascript\nrectangular_or_polar\n rectangular_or_polar_example\n attach_tag\n\nfunction is_rectangular(z) {\n return type_tag(z) === \"rectangular\";\n}\nfunction is_polar(z) {\n return type_tag(z) === \"polar\";\n}\n```\n\nWith type tags, Ben and Alyssa can now modify their code so that their two\ndifferent representations can coexist in the same system.\n\nWhenever Ben\nconstructs a complex number, he tags it as rectangular.\n\nWhenever Alyssa\nconstructs a complex number, she tags it as polar.\n\nIn addition, Ben and\nAlyssa must make sure that the names of their\nfunctions\ndo not conflict.\n\nOne way to do this is for Ben to append the suffix\nto the name of each of his\nrepresentation\nfunctions\nand for Alyssa to append to the names of\nhers.\n\nHere is Ben s revised rectangular representation from\nsection :\n\n```javascript\nmake_complex_number_rectangular\n attach_tag\n square_definition\n make_complex_number_rectangular_example\n 1.932653061713073\n\nfunction real_part_rectangular(z) { return head(z); }\n\nfunction imag_part_rectangular(z) { return tail(z); }\n\nfunction magnitude_rectangular(z) {\n return math_sqrt(square(real_part_rectangular(z)) +\n square(imag_part_rectangular(z)));\n}\nfunction angle_rectangular(z) {\n return math_atan2(imag_part_rectangular(z),\n real_part_rectangular(z));\n}\nfunction make_from_real_imag_rectangular(x, y) {\n return attach_tag(\"rectangular\", pair(x, y));\n}\nfunction make_from_mag_ang_rectangular(r, a) {\n return attach_tag(\"rectangular\",\n pair(r * math_cos(a), r * math_sin(a)));\n}\n```\n\n```javascript\nmake_complex_number_rectangular_example\n\nconst bens_co_num = make_from_mag_ang_rectangular(\n 3.0, 0.7);\n\nimag_part_rectangular(contents(bens_co_num));\n```\n\nand here is Alyssa s revised polar representation:\n\n```javascript\nmake_complex_number_polar\n attach_tag\n square_definition\n make_complex_number_polar_example\n 1.932653061713073\n\nfunction real_part_polar(z) {\n return magnitude_polar(z) * math_cos(angle_polar(z));\n}\nfunction imag_part_polar(z) {\n return magnitude_polar(z) * math_sin(angle_polar(z));\n}\nfunction magnitude_polar(z) { return head(z); }\n\nfunction angle_polar(z) { return tail(z); }\n\nfunction make_from_real_imag_polar(x, y) {\n return attach_tag(\"polar\",\n pair(math_sqrt(square(x) + square(y)),\n math_atan2(y, x)));\n}\n\nfunction make_from_mag_ang_polar(r, a) {\n return attach_tag(\"polar\", pair(r, a));\n}\n```\n\n```javascript\nmake_complex_number_polar_example\n\nconst alyssas_co_num = make_from_mag_ang_polar(\n 3.0, 0.7);\n\nimag_part_polar(contents(alyssas_co_num));\n```", + "token_count": 306, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", @@ -640,8 +660,8 @@ "chunk_id": "Building_Abstractions_with_Data_Tagged_data_2" }, { - "content": "and here is Alyssa s revised polar representation:\n\n```javascript\nmake_complex_number_polar\n attach_tag\n square_definition\n make_complex_number_polar_example\n 1.932653061713073\n\nfunction real_part_polar(z) {\n return magnitude_polar(z) * math_cos(angle_polar(z));\n}\nfunction imag_part_polar(z) {\n return magnitude_polar(z) * math_sin(angle_polar(z));\n}\nfunction magnitude_polar(z) { return head(z); }\n\nfunction angle_polar(z) { return tail(z); }\n\nfunction make_from_real_imag_polar(x, y) {\n return attach_tag(\"polar\",\n pair(math_sqrt(square(x) + square(y)),\n math_atan2(y, x)));\n}\n\nfunction make_from_mag_ang_polar(r, a) {\n return attach_tag(\"polar\", pair(r, a));\n}\n```\n\n```javascript\nmake_complex_number_polar_example\n\nconst alyssas_co_num = make_from_mag_ang_polar(\n 3.0, 0.7);\n\nimag_part_polar(contents(alyssas_co_num));\n```\n\nfunction\nthat checks the tag of its argument and calls the appropriate\nfunction\nfor handling data of that type.\n\nFor example, to obtain the real part of\na complex number,\nreal_part\nexamines the tag to determine whether to use Ben s\nreal_part_rectangular\nor Alyssa s\nreal_@part_@polar.\n\nIn either case, we use\nfunction\nas required:\n\n```javascript\nmake_complex_number\n rectangular_or_polar\n make_complex_number_rectangular\n make_complex_number_polar\n make_complex_number_example_2\n 1.932653061713073\n\nfunction real_part(z) {\n return is_rectangular(z)\n ? real_part_rectangular(contents(z))\n : is_polar(z)\n ? real_part_polar(contents(z))\n : error(z, \"unknown type -- real_part\");\n}\nfunction imag_part(z) {\n return is_rectangular(z)\n ? imag_part_rectangular(contents(z))\n : is_polar(z)\n ? imag_part_polar(contents(z))\n : error(z, \"unknown type -- imag_part\");\n}\nfunction magnitude(z) {\n return is_rectangular(z)\n ? magnitude_rectangular(contents(z))\n : is_polar(z)\n ? magnitude_polar(contents(z))\n : error(z, \"unknown type -- magnitude\");\n}\nfunction angle(z) {\n return is_rectangular(z)\n ? angle_rectangular(contents(z))\n : is_polar(z)\n ? angle_polar(contents(z))\n : error(z, \"unknown type -- angle\");\n}\n```\n\n```javascript\nmake_complex_number_example_2\n\nconst alyssas_co_num = make_from_mag_ang_polar(\n 3.0, 0.7);\n\nimag_part(alyssas_co_num);\n```\n\nTo implement the complex-number arithmetic operations, we can use the same\nfunctions\nadd_complex,\nsub_complex,\nmul_complex,\nand\ndiv_complex\nfrom section ,\nbecause the selectors they call are generic, and so will work with either\nrepresentation.\n\nFor example, the\nfunction\nadd_complex\nis still\n\n```javascript\nfunction add_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) + real_part(z2),\n imag_part(z1) + imag_part(z2));\n}\n```\n\nFinally, we must choose whether to construct complex numbers using\nBen s representation or Alyssa s representation.", - "token_count": 290, + "content": "and here is Alyssa s revised polar representation:\n\nFor example, to obtain the real part of\na complex number,\nreal_part\nexamines the tag to determine whether to use Ben s\nreal_part_rectangular\nor Alyssa s\nreal_@part_@polar.\n\nIn either case, we use to extract the\nbare, untagged datum and send this to the rectangular or polar\nfunction\nas required:\n\n```javascript\nmake_complex_number\n rectangular_or_polar\n make_complex_number_rectangular\n make_complex_number_polar\n make_complex_number_example_2\n 1.932653061713073\n\nfunction real_part(z) {\n return is_rectangular(z)\n ? real_part_rectangular(contents(z))\n : is_polar(z)\n ? real_part_polar(contents(z))\n : error(z, \"unknown type -- real_part\");\n}\nfunction imag_part(z) {\n return is_rectangular(z)\n ? imag_part_rectangular(contents(z))\n : is_polar(z)\n ? imag_part_polar(contents(z))\n : error(z, \"unknown type -- imag_part\");\n}\nfunction magnitude(z) {\n return is_rectangular(z)\n ? magnitude_rectangular(contents(z))\n : is_polar(z)\n ? magnitude_polar(contents(z))\n : error(z, \"unknown type -- magnitude\");\n}\nfunction angle(z) {\n return is_rectangular(z)\n ? angle_rectangular(contents(z))\n : is_polar(z)\n ? angle_polar(contents(z))\n : error(z, \"unknown type -- angle\");\n}\n```\n\n```javascript\nmake_complex_number_example_2\n\nconst alyssas_co_num = make_from_mag_ang_polar(\n 3.0, 0.7);\n\nimag_part(alyssas_co_num);\n```\n\nTo implement the complex-number arithmetic operations, we can use the same\nfunctions\nadd_complex,\nsub_complex,\nmul_complex,\nand\ndiv_complex\nfrom section ,\nbecause the selectors they call are generic, and so will work with either\nrepresentation.\n\nFor example, the\nfunction\nadd_complex\nis still\n\n```javascript\nfunction add_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) + real_part(z2),\n imag_part(z1) + imag_part(z2));\n}\n```\n\nFinally, we must choose whether to construct complex numbers using\nBen s representation or Alyssa s representation.\n\nOne\nreasonable choice is to construct rectangular numbers whenever we have\nreal and imaginary parts and to construct polar numbers whenever we have\nmagnitudes and angles:\n\n```javascript\nmake_complex_number_generic\n make_complex_number\n make_complex_number_rectangular\n make_complex_number_polar\n make_complex_number_generic_example\n 1.932653061713073\n\nfunction make_from_real_imag(x, y) {\n return make_from_real_imag_rectangular(x, y);\n}\nfunction make_from_mag_ang(r, a) {\n return make_from_mag_ang_polar(r, a);\n}\n```\n\n```javascript\nmake_complex_number_generic_example\n\nconst alyssas_co_num = make_from_mag_ang(\n 3.0, 0.7);\n\nimag_part(alyssas_co_num);\n```\n\nStructure of the generic complex-arithmetic system.\n$\\!$ The resulting complex-number system has the structure shown in\nfigure.", + "token_count": 296, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", @@ -650,9 +670,9 @@ "chunk_id": "Building_Abstractions_with_Data_Tagged_data_3" }, { - "content": "Finally, we must choose whether to construct complex numbers using\nBen s representation or Alyssa s representation.\n\nOne\nreasonable choice is to construct rectangular numbers whenever we have\nreal and imaginary parts and to construct polar numbers whenever we have\nmagnitudes and angles:\n\n```javascript\nmake_complex_number_generic\n make_complex_number\n make_complex_number_rectangular\n make_complex_number_polar\n make_complex_number_generic_example\n 1.932653061713073\n\nfunction make_from_real_imag(x, y) {\n return make_from_real_imag_rectangular(x, y);\n}\nfunction make_from_mag_ang(r, a) {\n return make_from_mag_ang_polar(r, a);\n}\n```\n\n```javascript\nmake_complex_number_generic_example\n\nconst alyssas_co_num = make_from_mag_ang(\n 3.0, 0.7);\n\nimag_part(alyssas_co_num);\n```\n\nStructure\n$\\!$ The resulting complex-number system has the structure shown in\nfigure.\n\nThe system has been decomposed into three relatively independent parts: the\ncomplex-number-arithmetic operations, Alyssa s polar implementation,\nand Ben s rectangular implementation.\n\nThe polar and rectangular\nimplementations could have been written by Ben and Alyssa working\nseparately, and both of these can be used as underlying representations by\na third programmer implementing the complex-arithmetic\nfunctions\nin terms of the abstract constructor/selector interface.\n\nSince each data object is tagged with its type, the selectors operate on\nthe data in a\ns polar\npackage) a complex number is an untyped pair (magnitude, angle).\n\nWhen a\ngeneric selector operates on a number of s\ncode.\n\nConversely, when Alyssa constructs a number for general use, she\ntags it with a type so that it can be appropriately recognized by the\nhigher-level\nfunctions.\n\nThis discipline of stripping off and attaching tags as data objects are\npassed from level to level can be an important organizational strategy,\nas we shall see in section.", - "token_count": 246, - "has_code": true, + "content": "Structure of the generic complex-arithmetic system.\n$\\!$ The resulting complex-number system has the structure shown in\nfigure.\n\nThe polar and rectangular\nimplementations could have been written by Ben and Alyssa working\nseparately, and both of these can be used as underlying representations by\na third programmer implementing the complex-arithmetic\nfunctions\nin terms of the abstract constructor/selector interface.\n\nSince each data object is tagged with its type, the selectors operate on\nthe data in a\ngeneric manner.\n\nThat is, each selector is defined to have a\nbehavior that depends upon the particular type of data it is applied to.\n\nNotice the general mechanism for interfacing the separate representations:\nWithin a given representation implementation (say, Alyssa s polar\npackage) a complex number is an untyped pair (magnitude, angle).\n\nWhen a\ngeneric selector operates on a number of\ntype, it strips off the tag and passes the contents on to Alyssa s\ncode.\n\nConversely, when Alyssa constructs a number for general use, she\ntags it with a type so that it can be appropriately recognized by the\nhigher-level\nfunctions.\n\nThis discipline of stripping off and attaching tags as data objects are\npassed from level to level can be an important organizational strategy,\nas we shall see in section.", + "token_count": 205, + "has_code": false, "chapter": "Building Abstractions with Data", "section": "Multiple Representations for Abstract Data", "subsection": "Tagged data", @@ -660,8 +680,8 @@ "chunk_id": "Building_Abstractions_with_Data_Tagged_data_4" }, { - "content": "This section provides practice in the use of list structure and data\nabstraction to manipulate sets and trees.\n\nThe application is to\nmethods for representing data as sequences of ones and zeros (bits).\n\nFor example, the\n$2^7$ , or\n128, possible different characters.\n\nIn general, if we want to distinguish\n$n$ different symbols, we will need to use\n$\\log_2 n$ bits per symbol.\n\nIf all our messages\nare made up of the eight symbols A, B, C, D, E, F, G, and H, we can choose\na code with three bits per character, for example\nA\n000\nC\n010\nE\n100\nG\n110\nB\n001\nD\n011\nF\n101\nH\n111\nWith this code, the message\nBACADAEAFABBAAAGAH\nis encoded as the string of 54 bits\n001000010000011000100000101000001001000000000110000111\n\nCodes such as ASCII and the A-through-H code above are known as\nfixed-length codes, because they represent each symbol in the\nmessage with the same number of bits.\n\nIt is sometimes advantageous to use\nvariable-length codes, in which different symbols may be\nrepresented by different numbers of bits.\n\nFor example,\nA\n0\nC\n1010\nE\n1100\nG\n1110\nB\n100\nD\n1011\nF\n1101\nH\n1111\nWith this code, the same message as above is encoded as the string\n100010100101101100011010100100000111001111\nThis string contains 42 bits, so it saves more than 20% in space in\ncomparison with the fixed-length code shown above.\n\nOne of the difficulties of using a variable-length code is knowing\nwhen you have reached the end of a symbol in reading a sequence of\nzeros and ones.\n\nMorse code solves this problem by using a special\nseparator code (in this case, a pause) after the sequence of\ndots and dashes for each letter.", - "token_count": 281, + "content": "This section provides practice in the use of list structure and data\nabstraction to manipulate sets and trees.\n\nThe application is to\nmethods for representing data as sequences of ones and zeros (bits).\n\nFor example, the\nASCII standard code used to represent text in\ncomputers encodes each\ncharacter as a sequence of seven bits.\n\nUsing\nseven bits allows us to distinguish $2^7$ , or\n128, possible different characters.\n\nIn general, if we want to distinguish\n$n$ different symbols, we will need to use\n$\\log_2 n$ bits per symbol.\n\nIf all our messages\nare made up of the eight symbols A, B, C, D, E, F, G, and H, we can choose\na code with three bits per character, for example\nA\n000\nC\n010\nE\n100\nG\n110\nB\n001\nD\n011\nF\n101\nH\n111\nWith this code, the message\nBACADAEAFABBAAAGAH\nis encoded as the string of 54 bits\n001000010000011000100000101000001001000000000110000111\n\nCodes such as ASCII and the A-through-H code above are known as\nfixed-length codes, because they represent each symbol in the\nmessage with the same number of bits.\n\nIt is sometimes advantageous to use\nvariable-length codes, in which different symbols may be\nrepresented by different numbers of bits.\n\nFor example,\nMorse code does not use the same number of dots and dashes for each letter\nof the alphabet.\n\nIn particular, E, the most frequent letter, is represented\nby a single dot.\n\nIn general, if our messages are such that some symbols\nappear very frequently and some very rarely, we can encode data more\nefficiently (i.e., using fewer bits per message) if we assign shorter\ncodes to the frequent symbols.", + "token_count": 270, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", @@ -670,8 +690,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_1" }, { - "content": "Morse code solves this problem by using a special\nseparator code (in this case, a pause) after the sequence of\ndots and dashes for each letter.\n\nAnother solution is to design the\ncode in such a way that no complete code for any symbol is the\nbeginning (or prefix ) of the code for another symbol.\n\nSuch a\ncode is called a\nprefix code.\n\nIn the example above, A is encoded by 0 and B is\nencoded by 100, so no other symbol can have a code that begins with 0 or\nwith 100.\n\nIn general, we can attain significant savings if we use\nvariable-length prefix codes that take advantage of the relative\nfrequencies of the symbols in the messages to be encoded.\n\nOne\nparticular for doing this is called the Huffman encoding\nmethod, after its discoverer,\n\nA Huffman encoding tree.\n\nFigure shows the Huffman tree for the\nA-through-H code given above.\n\nThe weights at the leaves indicate that the\ntree was designed for messages in which A appears with relative frequency\n8, B with relative frequency 3, and the other letters each with relative\nfrequency 1.\n\nGiven a Huffman tree, we can find the encoding of any symbol by\nstarting at the root and moving down until we reach the leaf that\nholds the symbol.\n\nEach time we move down a left branch we add a 0 to\nthe code, and each time we move down a right branch we add a 1.", - "token_count": 244, + "content": "In general, if our messages are such that some symbols\nappear very frequently and some very rarely, we can encode data more\nefficiently (i.e., using fewer bits per message) if we assign shorter\ncodes to the frequent symbols.\n\nOne of the difficulties of using a variable-length code is knowing\nwhen you have reached the end of a symbol in reading a sequence of\nzeros and ones.\n\nMorse code solves this problem by using a special\nseparator code (in this case, a pause) after the sequence of\ndots and dashes for each letter.\n\nAnother solution is to design the\ncode in such a way that no complete code for any symbol is the\nbeginning (or prefix ) of the code for another symbol.\n\nSuch a\ncode is called a\nprefix code.\n\nIn the example above, A is encoded by 0 and B is\nencoded by 100, so no other symbol can have a code that begins with 0 or\nwith 100.\n\nIn general, we can attain significant savings if we use\nvariable-length prefix codes that take advantage of the relative\nfrequencies of the symbols in the messages to be encoded.\n\nOne\nparticular for doing this is called the Huffman encoding\nmethod, after its discoverer,\nDavid Huffman.\n\nA Huffman code can be represented as a\nbinary tree whose leaves are the symbols that are encoded.\n\nAt each\nnon-leaf node of the tree there is a set containing all the symbols in the\nleaves that lie below the node.\n\nIn addition, each symbol at a leaf is\nassigned a weight (which is its relative frequency), and each non-leaf\nnode contains a weight that is the sum of all the weights of the leaves\nlying below it.\n\nThe weights are not used in the encoding or the decoding\nprocess.", + "token_count": 295, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", @@ -680,8 +700,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_2" }, { - "content": "Each time we move down a left branch we add a 0 to\nthe code, and each time we move down a right branch we add a 1.\n\n(We\ndecide which branch to follow by testing to see which branch either is\nthe leaf node for the symbol or contains the symbol in its set.) For\nexample, starting from the root of the tree in\nfigure , we arrive at the leaf for D by\nfollowing a right branch, then a left branch, then a right branch, then a\nright branch; hence, the code for D is 1011.\n\nTo decode a bit sequence using a Huffman tree, we begin at the root\nand use the successive zeros and ones of the bit sequence to determine\nwhether to move down the left or the right branch.\n\nEach time we come\nto a leaf, we have generated a new symbol in the message, at which\npoint we start over from the root of the tree to find the next symbol.\n\nFor example, suppose we are given the tree above and the sequence\n10001010.\n\nStarting at the root, we move down the right branch (since\nthe first bit of the string is 1), then down the left branch (since\nthe second bit is 0), then down the left branch (since the third bit\nis also 0).\n\nThis brings us to the leaf for B, so the first\nsymbol of the decoded message is B.\n\nNow we start again at the root,\nand we make a left move because the next bit in the string is 0.\n\nThis brings us to the leaf for A.\n\nThen we start again at the root\nwith the rest of the string 1010, so we move right, left, right, left and\nreach C.", - "token_count": 295, + "content": "The weights are not used in the encoding or the decoding\nprocess.\n\nA Huffman encoding tree.\n\nFigure shows the Huffman tree for the\nA-through-H code given above.\n\nThe weights at the leaves indicate that the\ntree was designed for messages in which A appears with relative frequency\n8, B with relative frequency 3, and the other letters each with relative\nfrequency 1.\n\nGiven a Huffman tree, we can find the encoding of any symbol by\nstarting at the root and moving down until we reach the leaf that\nholds the symbol.\n\nEach time we move down a left branch we add a 0 to\nthe code, and each time we move down a right branch we add a 1.\n\n(We\ndecide which branch to follow by testing to see which branch either is\nthe leaf node for the symbol or contains the symbol in its set.) For\nexample, starting from the root of the tree in\nfigure , we arrive at the leaf for D by\nfollowing a right branch, then a left branch, then a right branch, then a\nright branch; hence, the code for D is 1011.\n\nTo decode a bit sequence using a Huffman tree, we begin at the root\nand use the successive zeros and ones of the bit sequence to determine\nwhether to move down the left or the right branch.\n\nEach time we come\nto a leaf, we have generated a new symbol in the message, at which\npoint we start over from the root of the tree to find the next symbol.\n\nFor example, suppose we are given the tree above and the sequence\n10001010.", + "token_count": 272, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", @@ -690,8 +710,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_3" }, { - "content": "Then we start again at the root\nwith the rest of the string 1010, so we move right, left, right, left and\nreach C.\n\nThus, the entire message is BAC.\n\nGiven an alphabet of symbols and their relative frequencies,\nhow do we construct the best code?\n\n(In other words, which\ntree will encode messages with the fewest bits?) Huffman gave an algorithm\nfor doing this and showed that the resulting code is indeed the best\nvariable-length code for messages where the relative frequency of the\nsymbols matches the frequencies with which the code was constructed.\n\nThe algorithm for generating a Huffman tree is very simple.\n\nThe idea\nis to arrange the tree so that the symbols with the lowest frequency\nappear farthest away from the root.\n\nBegin with the set of leaf nodes,\ncontaining symbols and their frequencies, as determined by the initial data\nfrom which the code is to be constructed.\n\nNow find two leaves with\nthe lowest weights and merge them to produce a node that has these\ntwo nodes as its left and right branches.\n\nThe weight of the new node\nis the sum of the two weights.\n\nRemove the two leaves from the\noriginal set and replace them by this new node.\n\nNow continue this\nprocess.\n\nAt each step, merge two nodes with the smallest weights,\nremoving them from the set and replacing them with a node that has\nthese two as its left and right branches.\n\nThe process stops when\nthere is only one node left, which is the root of the entire tree.", - "token_count": 259, + "content": "For example, suppose we are given the tree above and the sequence\n10001010.\n\nThis brings us to the leaf for B, so the first\nsymbol of the decoded message is B.\n\nNow we start again at the root,\nand we make a left move because the next bit in the string is 0.\n\nThis brings us to the leaf for A.\n\nThen we start again at the root\nwith the rest of the string 1010, so we move right, left, right, left and\nreach C.\n\nThus, the entire message is BAC.\n\nGiven an alphabet of symbols and their relative frequencies,\nhow do we construct the best code?\n\n(In other words, which\ntree will encode messages with the fewest bits?) Huffman gave an algorithm\nfor doing this and showed that the resulting code is indeed the best\nvariable-length code for messages where the relative frequency of the\nsymbols matches the frequencies with which the code was constructed.\n\nWe will not prove this optimality of Huffman codes here, but we will\nshow how Huffman trees are constructed.\n\nThe algorithm for generating a Huffman tree is very simple.\n\nThe idea\nis to arrange the tree so that the symbols with the lowest frequency\nappear farthest away from the root.\n\nBegin with the set of leaf nodes,\ncontaining symbols and their frequencies, as determined by the initial data\nfrom which the code is to be constructed.\n\nNow find two leaves with\nthe lowest weights and merge them to produce a node that has these\ntwo nodes as its left and right branches.\n\nThe weight of the new node\nis the sum of the two weights.\n\nRemove the two leaves from the\noriginal set and replace them by this new node.\n\nNow continue this\nprocess.", + "token_count": 290, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", @@ -700,8 +720,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_4" }, { - "content": "The process stops when\nthere is only one node left, which is the root of the entire tree.\n\nHere is how the Huffman tree of figure was\ngenerated:\nInitial leaves\n$\\{$ (A 8) (B 3) (C 1) (D 1) (E 1) (F 1) (G 1) (H 1) $\\}$\nMerge\n$\\{$ (A 8) (B 3) ( $\\{$ C D $\\}$ 2) (E 1) (F 1) (G 1) (H 1) $\\}$\nMerge\n$\\{$ (A 8) (B 3) ( $\\{$ C D $\\}$ 2) ( $\\{$ E F $\\}$ 2) (G 1) (H 1) $\\}$\nMerge\n$\\{$ (A 8) (B 3) ( $\\{$ C D $\\}$ 2) ( $\\{$ E F $\\}$ 2) ( $\\{$ G H $\\}$ 2) $\\}$\nMerge\n$\\{$ (A 8) (B 3) ( $\\{$ C D $\\}$ 2) ( $\\{$ E F G H $\\}$ 4) $\\}$\nMerge\n$\\{$ (A 8) ( $\\{$ B C D $\\}$ 5) ( $\\{$ E F G H $\\}$ 4) $\\}$\nMerge\n$\\{$ (A 8) ( $\\{$ B C D E F G H $\\}$ 9) $\\}$\nFinal merge\n$\\{$ ( $\\{$ A B C D E F G H $\\}$ 17) $\\}$\nThe algorithm does not always specify a unique tree, because there may\nnot be unique smallest-weight nodes at each step.\n\nAlso, the choice of\nthe order in which the two nodes are merged (i.e., which will be the\nright branch and which will be the left branch) is arbitrary.\n\nIn the exercises below we will work with a system that uses\nHuffman trees to encode and decode messages and generates Huffman\ntrees according to the algorithm outlined above.\n\nWe will begin by\ndiscussing how trees are represented.\n\nLeaves of the tree are represented by a list consisting of the string \"leaf\", the symbol at the leaf, and the weight:", - "token_count": 299, + "content": "Now continue this\nprocess.\n\nThe process stops when\nthere is only one node left, which is the root of the entire tree.\n\nHere is how the Huffman tree of figure was\ngenerated:\nInitial leaves\n$\\{$ (A 8) (B 3) (C 1) (D 1) (E 1) (F 1) (G 1) (H 1) $\\}$\nMerge\n$\\{$ (A 8) (B 3) ( $\\{$ C D $\\}$ 2) (E 1) (F 1) (G 1) (H 1) $\\}$\nMerge\n$\\{$ (A 8) (B 3) ( $\\{$ C D $\\}$ 2) ( $\\{$ E F $\\}$ 2) (G 1) (H 1) $\\}$\nMerge\n$\\{$ (A 8) (B 3) ( $\\{$ C D $\\}$ 2) ( $\\{$ E F $\\}$ 2) ( $\\{$ G H $\\}$ 2) $\\}$\nMerge\n$\\{$ (A 8) (B 3) ( $\\{$ C D $\\}$ 2) ( $\\{$ E F G H $\\}$ 4) $\\}$\nMerge\n$\\{$ (A 8) ( $\\{$ B C D $\\}$ 5) ( $\\{$ E F G H $\\}$ 4) $\\}$\nMerge\n$\\{$ (A 8) ( $\\{$ B C D E F G H $\\}$ 9) $\\}$\nFinal merge\n$\\{$ ( $\\{$ A B C D E F G H $\\}$ 17) $\\}$\nThe algorithm does not always specify a unique tree, because there may\nnot be unique smallest-weight nodes at each step.\n\nAlso, the choice of\nthe order in which the two nodes are merged (i.e., which will be the\nright branch and which will be the left branch) is arbitrary.\n\nIn the exercises below we will work with a system that uses\nHuffman trees to encode and decode messages and generates Huffman\ntrees according to the algorithm outlined above.\n\nWe will begin by\ndiscussing how trees are represented.", + "token_count": 281, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", @@ -710,8 +730,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_5" }, { - "content": "Leaves of the tree are represented by a list consisting of the string \"leaf\", the symbol at the leaf, and the weight:\n\n```javascript\nmake_leaf\n make_leaf_example\n 8\n\nfunction make_leaf(symbol, weight) {\n return list(\"leaf\", symbol, weight);\n}\nfunction is_leaf(object) {\n return head(object) === \"leaf\";\n}\nfunction symbol_leaf(x) { return head(tail(x)); }\n\nfunction weight_leaf(x) { return head(tail(tail(x))); }\n```\n\n```javascript\nmake_leaf_example\n\nconst my_leaf = make_leaf(\"A\", 8);\n\nweight_leaf(my_leaf);\n```\n\nA general tree will be a list of\na string \"code_tree\",\na left branch, a right branch, a set\nof symbols, and a weight.\n\nThe set of symbols will be simply a list of\nthe symbols, rather than some more sophisticated set representation.\n\nWhen we make a tree by merging two nodes, we obtain the weight of the\ntree as the sum of the weights of the nodes, and the set of symbols as\nthe union of the sets of symbols for the nodes.\n\nSince our symbol sets are\nrepresented as lists, we can form the union by using the\nfunction\nwe defined in section :\n\n```javascript\nmake_code_tree\n make_code_tree_example\n [ 'leaf', [ 'A', [ 8, null ] ] ]\n tree_property\n\nfunction make_code_tree(left, right) {\n return list(\"code_tree\", left, right,\n append(symbols(left), symbols(right)),\n weight(left) + weight(right));\n}\n```\n\n```javascript\nmake_code_tree_example\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nmake_code_tree(my_leaf_1, my_leaf_2);\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nhead(tail(make_code_tree(my_leaf_1, my_leaf_2)));\n```\n\nIf we make a tree in this way, we have the following selectors:\n\n```javascript\ntree_property\n make_leaf\n tree_property_example\n 11\n\nfunction left_branch(tree) { return head(tail(tree)); }\n\nfunction right_branch(tree) { return head(tail(tail(tree))); }\n\nfunction symbols(tree) {\n return is_leaf(tree)\n ? list(symbol_leaf(tree))\n : head(tail(tail(tail(tree))));\n}\nfunction weight(tree) {\n return is_leaf(tree)\n ? weight_leaf(tree)\n : head(tail(tail(tail(tail(tree)))));\n}\n```\n\n```javascript\ntree_property_example\n make_code_tree\n make_leaf\n tree_property_example\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\nconst my_tree = make_code_tree(my_leaf_1, my_leaf_2);\n\nweight(my_tree);\n```", - "token_count": 300, + "content": "We will begin by\ndiscussing how trees are represented.\n\n```javascript\nmake_leaf\n make_leaf_example\n 8\n\nfunction make_leaf(symbol, weight) {\n return list(\"leaf\", symbol, weight);\n}\nfunction is_leaf(object) {\n return head(object) === \"leaf\";\n}\nfunction symbol_leaf(x) { return head(tail(x)); }\n\nfunction weight_leaf(x) { return head(tail(tail(x))); }\n```\n\n```javascript\nmake_leaf_example\n\nconst my_leaf = make_leaf(\"A\", 8);\n\nweight_leaf(my_leaf);\n```\n\nA general tree will be a list of\na string \"code_tree\",\na left branch, a right branch, a set\nof symbols, and a weight.\n\nThe set of symbols will be simply a list of\nthe symbols, rather than some more sophisticated set representation.\n\nWhen we make a tree by merging two nodes, we obtain the weight of the\ntree as the sum of the weights of the nodes, and the set of symbols as\nthe union of the sets of symbols for the nodes.\n\nSince our symbol sets are\nrepresented as lists, we can form the union by using the\nfunction\nwe defined in section :\n\n```javascript\nmake_code_tree\n make_code_tree_example\n [ 'leaf', [ 'A', [ 8, null ] ] ]\n tree_property\n\nfunction make_code_tree(left, right) {\n return list(\"code_tree\", left, right,\n append(symbols(left), symbols(right)),\n weight(left) + weight(right));\n}\n```\n\n```javascript\nmake_code_tree_example\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nmake_code_tree(my_leaf_1, my_leaf_2);\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nhead(tail(make_code_tree(my_leaf_1, my_leaf_2)));\n```\n\nIf we make a tree in this way, we have the following selectors:\n\n```javascript\ntree_property\n make_leaf\n tree_property_example\n 11\n\nfunction left_branch(tree) { return head(tail(tree)); }\n\nfunction right_branch(tree) { return head(tail(tail(tree))); }\n\nfunction symbols(tree) {\n return is_leaf(tree)\n ? list(symbol_leaf(tree))\n : head(tail(tail(tail(tree))));\n}\nfunction weight(tree) {\n return is_leaf(tree)\n ? weight_leaf(tree)\n : head(tail(tail(tail(tail(tree)))));\n}\n```\n\n```javascript\ntree_property_example\n make_code_tree\n make_leaf\n tree_property_example\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\nconst my_tree = make_code_tree(my_leaf_1, my_leaf_2);\n\nweight(my_tree);\n```\n\nThe\nfunctions\nand\nmust do something slightly different\ndepending on whether they are called with a leaf or a general tree.", + "token_count": 308, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", @@ -720,8 +740,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_6" }, { - "content": "If we make a tree in this way, we have the following selectors:\n\nThe functions generic functions (functions that can handle more than one kind of data), which we will have much more to say about in sections\n\nand.\n\nThe following\nfunction\nimplements the decoding algorithm.\n\nIt takes as arguments a list of zeros\nand ones, together with a Huffman tree.\n\n```javascript\ndecode_function\n make_leaf\n make_code_tree\n tree_property\n decode_function_example\n [ 'B', [ 'B', [ 'A', null ] ] ]\n\nfunction decode(bits, tree) {\n function decode_1(bits, current_branch) {\n if (is_null(bits)) {\n return null;\n } else {\n const next_branch = choose_branch(head(bits),\n current_branch);\n return is_leaf(next_branch)\n ? pair(symbol_leaf(next_branch),\n decode_1(tail(bits), tree))\n : decode_1(tail(bits), next_branch);\n }\n }\n return decode_1(bits, tree);\n}\n\nfunction choose_branch(bit, branch) {\n return bit === 0\n ? left_branch(branch)\n : bit === 1\n ? right_branch(branch)\n : error(bit, \"bad bit -- choose_branch\");\n}\n```\n\n```javascript\ndecode_function_example\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nconst my_tree = make_code_tree(my_leaf_1, my_leaf_2);\n\ndecode(list(0, 1, 1, 0), my_tree);\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nconst my_tree = make_code_tree(my_leaf_1, my_leaf_2);\n\ntail(decode(list(0, 1, 1, 0), my_tree));\n```\n\nThe\nfunction\ndecode_1\ntakes two arguments: the list of remaining bits and the current position in\nthe tree.\n\nIt keeps moving down the tree, choosing a left or\na right branch according to whether the next bit in the list is a zero or a\none.\n\n(This is done with the\nfunction\nchoose_branch.)\nWhen it reaches a leaf, it returns the symbol at that leaf as the next\nsymbol in the message by\n\n```javascript\nadjoining\n\tit to the result of decoding the rest of the message,\n\tstarting at the root of the tree.\n```\n\nNote the error check in the final clause of choose_branch, which complains if the function finds something other than a zero or a one in\n\nthe input data.", - "token_count": 302, + "content": "The\nfunctions\nand\nmust do something slightly different\ndepending on whether they are called with a leaf or a general tree.\n\nThe following\nfunction\nimplements the decoding algorithm.\n\nIt takes as arguments a list of zeros\nand ones, together with a Huffman tree.\n\n```javascript\ndecode_function\n make_leaf\n make_code_tree\n tree_property\n decode_function_example\n [ 'B', [ 'B', [ 'A', null ] ] ]\n\nfunction decode(bits, tree) {\n function decode_1(bits, current_branch) {\n if (is_null(bits)) {\n return null;\n } else {\n const next_branch = choose_branch(head(bits),\n current_branch);\n return is_leaf(next_branch)\n ? pair(symbol_leaf(next_branch),\n decode_1(tail(bits), tree))\n : decode_1(tail(bits), next_branch);\n }\n }\n return decode_1(bits, tree);\n}\n\nfunction choose_branch(bit, branch) {\n return bit === 0\n ? left_branch(branch)\n : bit === 1\n ? right_branch(branch)\n : error(bit, \"bad bit -- choose_branch\");\n}\n```\n\n```javascript\ndecode_function_example\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nconst my_tree = make_code_tree(my_leaf_1, my_leaf_2);\n\ndecode(list(0, 1, 1, 0), my_tree);\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nconst my_tree = make_code_tree(my_leaf_1, my_leaf_2);\n\ntail(decode(list(0, 1, 1, 0), my_tree));\n```\n\nThe\nfunction\ndecode_1\ntakes two arguments: the list of remaining bits and the current position in\nthe tree.\n\nIt keeps moving down the tree, choosing a left or\na right branch according to whether the next bit in the list is a zero or a\none.\n\n(This is done with the\nfunction\nchoose_branch.)\nWhen it reaches a leaf, it returns the symbol at that leaf as the next\nsymbol in the message by\nadjoining it to the result of decoding the rest of the message, starting at the root of the tree.\n\nNote the error check in the final clause of\nchoose_branch,\nwhich complains if the\nfunction\nfinds something other than a zero or a one in the input data.\n\nIn our representation of trees, each non-leaf node contains a set of\nsymbols, which we have represented as a simple list.", + "token_count": 303, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", @@ -730,8 +750,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_7" }, { - "content": "the input data.\n\nIn our representation of trees, each non-leaf node contains a set of\nsymbols, which we have represented as a simple list.\n\nHowever, the\ntree-generating algorithm discussed above requires that we also work\nwith sets of leaves and trees, successively merging the two smallest\nitems.\n\nSince we will be required to repeatedly find the smallest item\nin a set, it is convenient to use an ordered representation for this\nkind of set.\n\nWe will represent a set of leaves and trees as a list of elements,\narranged in increasing order of weight.\n\nThe following\nadjoin_set\nfunction\nfor constructing sets is similar to the one\ndescribed in exercise ; however, items\nare compared by their weights, and the element being added to the set is\nnever already in it.\n\n```javascript\nadjoin_set3\n tree_property\n adjoin_set3_example\n [ 'leaf', [ 'B', [ 3, null ] ] ]\n\nfunction adjoin_set(x, set) {\n return is_null(set)\n ? list(x)\n : weight(x) < weight(head(set))\n ? pair(x, set)\n : pair(head(set), adjoin_set(x, tail(set)));\n}\n```\n\n```javascript\nadjoin_set3_example\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nadjoin_set(my_leaf_1, adjoin_set(my_leaf_2, null));\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nhead(adjoin_set(my_leaf_1, adjoin_set(my_leaf_2, null)));\n```\n\nThe following function takes a list of symbol-frequency pairs such as list(list(\"A\", 4), list(\"B\", 2), list(\"C\", 1), list(\"D\", 1)) and constructs an initial ordered set\n\nof leaves, ready to be merged according to the Huffman algorithm:\n\n```javascript\nmake_leaf_set\n make_leaf\n adjoin_set3\n make_leaf_set_example\n [ 'leaf', [ 'leaf', [ 'A', null ] ] ]\n\nfunction make_leaf_set(pairs) {\n if (is_null(pairs)) {\n return null;\n } else {\n const first_pair = head(pairs);\n return adjoin_set(\n make_leaf(head(first_pair), // symbol\n head(tail(first_pair))), // frequency\n make_leaf_set(tail(pairs)));\n }\n}\n```\n\n```javascript\nmake_leaf_set_example\n\nmake_leaf_set( list( list(\"A\", 4),\n list(\"B\", 2),\n list(\"C\", 1),\n list(\"D\", 1) ) );\n\nhead(make_leaf_set( list( list(\"A\", 4),\n list(\"B\", 2),\n list(\"C\", 1),\n list(\"D\", 1) ) ) );\n```", - "token_count": 300, + "content": "In our representation of trees, each non-leaf node contains a set of\nsymbols, which we have represented as a simple list.\n\nSince we will be required to repeatedly find the smallest item\nin a set, it is convenient to use an ordered representation for this\nkind of set.\n\nWe will represent a set of leaves and trees as a list of elements,\narranged in increasing order of weight.\n\nThe following\nadjoin_set\nfunction\nfor constructing sets is similar to the one\ndescribed in exercise ; however, items\nare compared by their weights, and the element being added to the set is\nnever already in it.\n\n```javascript\nadjoin_set3\n tree_property\n adjoin_set3_example\n [ 'leaf', [ 'B', [ 3, null ] ] ]\n\nfunction adjoin_set(x, set) {\n return is_null(set)\n ? list(x)\n : weight(x) < weight(head(set))\n ? pair(x, set)\n : pair(head(set), adjoin_set(x, tail(set)));\n}\n```\n\n```javascript\nadjoin_set3_example\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nadjoin_set(my_leaf_1, adjoin_set(my_leaf_2, null));\n\nconst my_leaf_1 = make_leaf(\"A\", 8);\nconst my_leaf_2 = make_leaf(\"B\", 3);\n\nhead(adjoin_set(my_leaf_1, adjoin_set(my_leaf_2, null)));\n```\n\nThe following function takes a list of symbol-frequency pairs such as list(list(\"A\", 4), list(\"B\", 2), list(\"C\", 1), list(\"D\", 1)) and constructs an initial ordered set\n\nof leaves, ready to be merged according to the Huffman algorithm:\n\n```javascript\nmake_leaf_set\n make_leaf\n adjoin_set3\n make_leaf_set_example\n [ 'leaf', [ 'leaf', [ 'A', null ] ] ]\n\nfunction make_leaf_set(pairs) {\n if (is_null(pairs)) {\n return null;\n } else {\n const first_pair = head(pairs);\n return adjoin_set(\n make_leaf(head(first_pair), // symbol\n head(tail(first_pair))), // frequency\n make_leaf_set(tail(pairs)));\n }\n}\n```\n\n```javascript\nmake_leaf_set_example\n\nmake_leaf_set( list( list(\"A\", 4),\n list(\"B\", 2),\n list(\"C\", 1),\n list(\"D\", 1) ) );\n\nhead(make_leaf_set( list( list(\"A\", 4),\n list(\"B\", 2),\n list(\"C\", 1),\n list(\"D\", 1) ) ) );\n```\n\nThe tree will be unbalanced, similar to the tree given in\nfigure.\n\nEncoding the most\nfrequent symbol requires one bit, whereas\n$n - 1$ bits are required to encode the\nleast frequent symbol.", + "token_count": 307, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", @@ -740,28 +760,18 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_8" }, { - "content": "of leaves, ready to be merged according to the Huffman algorithm:\n\nThe tree will be unbalanced, similar to the tree given in\nfigure.\n\nEncoding the most\nfrequent symbol requires one bit, whereas\n$n - 1$ bits are required to encode the\nleast frequent symbol.", - "token_count": 44, + "content": "In the previous examples we built representations for two kinds of\ncompound data objects: rational numbers and algebraic expressions.\n\nIn\none of these examples we had the choice of simplifying (reducing) the\nexpressions at either construction time or selection time, but other\nthan that the choice of a representation for these structures in terms\nof lists was straightforward.\n\nWhen we turn to the representation of\nsets, the choice of a representation is not so obvious.\n\nIndeed, there\nare a number of possible representations, and they differ\nsignificantly from one another in several ways.\n\nInformally, a set is simply a collection of distinct objects.\n\nTo give\na more precise definition we can employ the method of data\nabstraction.\n\nThat is, we define set by specifying the\noperations that are to be used on sets.\n\nThese are\nunion_set,\nintersection_set,\nis_element_of_set,\nand\nadjoin_set.\n\nThe function is_@element_of_set\nis a predicate that determines whether a given element is a member of a set.\n\nThe function adjoin_@set\ntakes an object and a set as arguments and returns a set that contains the\nelements of the original set and also the adjoined element.\n\nThe function union_@set\ncomputes the union of two sets, which is the set containing each element\nthat appears in either argument.\n\nThe function intersection_@set\ncomputes the intersection of two sets, which is the set containing only\nelements that appear in both arguments.\n\nFrom the viewpoint of data\nabstraction, we are free to design any representation that implements these\noperations in a way consistent with the interpretations given\nabove.\n\nOne way to represent a set is as a list of its elements in which no\nelement appears more than once.\n\nThe empty set is represented by the\nempty list.\n\nIn this representation,\nis_element_of_set\nis similar to the\nfunction\nmember of section.", + "token_count": 296, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", - "subsection": "Example: Huffman Encoding Trees", - "chunk_index": 9, - "chunk_id": "Building_Abstractions_with_Data_Example_Huffman_Encoding_Trees_9" - }, - { - "content": "In the previous examples we built representations for two kinds of\ncompound data objects: rational numbers and algebraic expressions.\n\nIn\none of these examples we had the choice of simplifying (reducing) the\nexpressions at either construction time or selection time, but other\nthan that the choice of a representation for these structures in terms\nof lists was straightforward.\n\nWhen we turn to the representation of\nsets, the choice of a representation is not so obvious.\n\nIndeed, there\nare a number of possible representations, and they differ\nsignificantly from one another in several ways.\n\nInformally, a set is simply a collection of distinct objects.\n\nTo give\na more precise definition we can employ the method of data\nabstraction.\n\nThat is, we define set by specifying the\nunion_set,\nintersection_set,\nis_element_of_set,\nand\nadjoin_set.\n\nThe function is_@element_of_set\nis a predicate that determines whether a given element is a member of a set.\n\nThe function adjoin_@set\ntakes an object and a set as arguments and returns a set that contains the\nelements of the original set and also the adjoined element.\n\nThe function union_@set\ncomputes the union of two sets, which is the set containing each element\nthat appears in either argument.\n\n```javascript\nThe function\n intersection_@set\n```\n\ncomputes the intersection of two sets, which is the set containing only\nelements that appear in both arguments.\n\nFrom the viewpoint of data\nabstraction, we are free to design any representation that implements these\noperations in a way consistent with the interpretations given\nabove.\n\nOne way to represent a set is as a list of its elements in which no\nelement appears more than once.\n\nThe empty set is represented by the\nempty list.\n\nIn this representation,\nis_element_of_set\nis similar to the\nfunction\n\n```javascript\nmember\n of section.\n```\n\nIt uses equal instead of\n\n```javascript\n===\n```", - "token_count": 298, - "has_code": true, - "chapter": "Building Abstractions with Data", - "section": "Symbolic Data", "subsection": "Example: Representing Sets", "chunk_index": 1, "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_1" }, { - "content": "It uses equal instead of\n\nso that the set elements need not be just numbers or strings:\n\n```javascript\nis_element_of_set\n is_element_of_set_example\n true\n\nfunction is_element_of_set(x, set) {\n return is_null(set)\n ? false\n : equal(x, head(set))\n ? true\n : is_element_of_set(x, tail(set));\n}\n```\n\n```javascript\nis_element_of_set_example\n adjoin_set\n\nis_element_of_set(15,\n adjoin_set(10, adjoin_set(15, adjoin_set(20, null))));\n```\n\nUsing this, we can write\nadjoin_set.\n\nIf the object to be adjoined is already in the set, we just return the set.\n\nOtherwise, we use\npair\nto add the object to the list that represents the set:\n\n```javascript\nadjoin_set\n is_element_of_set\n adjoin_set_example\n [ 10, [ 15, [ 20, null ] ] ]\n\nfunction adjoin_set(x, set) {\n return is_element_of_set(x, set)\n ? set\n : pair(x, set);\n}\n```\n\n```javascript\nadjoin_set_example\n\nadjoin_set(10, adjoin_set(15, adjoin_set(20, null)));\n```\n\nFor\nintersection_set\nwe can use a recursive strategy.\n\nIf we know how to form the intersection\nof\ntail\nof\nhead\nof\nhead(set1)\nis also in\nfunction:\n\n```javascript\nintersection_set\n is_element_of_set\n intersection_set_example\n [ 10, [ 20, null ] ]\n\nfunction intersection_set(set1, set2) {\n return is_null(set1) || is_null(set2)\n ? null\n : is_element_of_set(head(set1), set2)\n ? pair(head(set1), intersection_set(tail(set1), set2))\n : intersection_set(tail(set1), set2);\n}\n```\n\n```javascript\nintersection_set_example\n adjoin_set\n intersection_set\n\nintersection_set(\n adjoin_set(10, adjoin_set(20, adjoin_set(30, null))),\n adjoin_set(10, adjoin_set(15, adjoin_set(20, null))));\n```\n\nIn designing a representation, one of the issues we should be concerned\nwith is efficiency.\n\nConsider the number of steps required by our set\noperations.\n\nSince they all use\nis_element_of_set,\nthe speed of this operation has a major impact on the efficiency of the set\nimplementation as a whole.\n\nNow, in order to check whether an object is a\nmember of a set,\nis_element_of_set\nmay have to scan the entire set.\n\n(In the worst case, the object turns out\nnot to be in the set.) Hence, if the set has\n$n$ elements,\nis_element_of_set\nmight take up to $n$ steps.\n\nThus, the number of\nsteps required grows as $\\Theta(n)$.", - "token_count": 301, + "content": "In this representation,\nis_element_of_set\nis similar to the\nfunction\nmember of section.\n\n```javascript\nis_element_of_set\n is_element_of_set_example\n true\n\nfunction is_element_of_set(x, set) {\n return is_null(set)\n ? false\n : equal(x, head(set))\n ? true\n : is_element_of_set(x, tail(set));\n}\n```\n\n```javascript\nis_element_of_set_example\n adjoin_set\n\nis_element_of_set(15,\n adjoin_set(10, adjoin_set(15, adjoin_set(20, null))));\n```\n\nUsing this, we can write\nadjoin_set.\n\nIf the object to be adjoined is already in the set, we just return the set.\n\nOtherwise, we use\npair\nto add the object to the list that represents the set:\n\n```javascript\nadjoin_set\n is_element_of_set\n adjoin_set_example\n [ 10, [ 15, [ 20, null ] ] ]\n\nfunction adjoin_set(x, set) {\n return is_element_of_set(x, set)\n ? set\n : pair(x, set);\n}\n```\n\n```javascript\nadjoin_set_example\n\nadjoin_set(10, adjoin_set(15, adjoin_set(20, null)));\n```\n\nFor\nintersection_set\nwe can use a recursive strategy.\n\nIf we know how to form the intersection\nof and the\ntail\nof , we only need to decide whether to\ninclude the\nhead\nof in this.\n\nBut this depends on whether\nhead(set1)\nis also in.\n\nHere is the resulting\nfunction:\n\n```javascript\nintersection_set\n is_element_of_set\n intersection_set_example\n [ 10, [ 20, null ] ]\n\nfunction intersection_set(set1, set2) {\n return is_null(set1) || is_null(set2)\n ? null\n : is_element_of_set(head(set1), set2)\n ? pair(head(set1), intersection_set(tail(set1), set2))\n : intersection_set(tail(set1), set2);\n}\n```\n\n```javascript\nintersection_set_example\n adjoin_set\n intersection_set\n\nintersection_set(\n adjoin_set(10, adjoin_set(20, adjoin_set(30, null))),\n adjoin_set(10, adjoin_set(15, adjoin_set(20, null))));\n```\n\nIn designing a representation, one of the issues we should be concerned\nwith is efficiency.\n\nConsider the number of steps required by our set\noperations.\n\nSince they all use\nis_element_of_set,\nthe speed of this operation has a major impact on the efficiency of the set\nimplementation as a whole.\n\nNow, in order to check whether an object is a\nmember of a set,\nis_element_of_set\nmay have to scan the entire set.\n\n(In the worst case, the object turns out\nnot to be in the set.) Hence, if the set has\n$n$ elements,\nis_element_of_set\nmight take up to $n$ steps.", + "token_count": 310, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", @@ -770,9 +780,9 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_2" }, { - "content": "Thus, the number of\nsteps required grows as $\\Theta(n)$.\n\nThe number\nof steps required by\nadjoin_set,\nwhich uses\nthis operation, also grows as $\\Theta(n)$.\n\nFor\nintersection_set,\nwhich does an\nis_element_of_set\ncheck for each element of $\\Theta(n^{2})$ for two sets of size\n$n$.\n\nThe same will be true of\nunion_set.\n\nOne way to speed up our set operations is to change the representation\nso that the set elements are listed in increasing order.\n\nTo do this,\nwe need some way to compare two objects so that we can say which is\nbigger.\n\nFor example, we could compare\nstrings\nlexicographically, or\nwe could agree on some method for assigning a unique number to an\nobject and then compare the elements by comparing the corresponding\nnumbers.\n\nTo keep our discussion simple, we will consider only the\ncase where the set elements are numbers, so that we can compare\nelements using $\\{1,3,6,10\\}$ by listing the elements in any\norder, our new representation allows only the list\nlist(1, 3, 6, 10).\n\nOne advantage of ordering shows up in\nis_element_of_set:\nIn checking for the presence of an item, we no longer have to scan the\nentire set.\n\nIf we reach a set element that is larger than the item we\nare looking for, then we know that the item is not in the set:\n\n```javascript\nx\n is_element_of_set2\n is_element_of_set_example_2\n true\n\nfunction is_element_of_set(x, set) {\n return is_null(set)\n ? false\n : x === head(set)\n ? true\n : x < head(set)\n ? false\n : // $\\texttt{x > head(set)}$\n is_element_of_set(x, tail(set));\n}\n```\n\n```javascript\nis_element_of_set_example_2\n\nis_element_of_set(15, list(10, 15, 20));\n```\n\nHow many steps does this save?\n\nIn the worst case, the item we are\nlooking for may be the largest one in the set, so the number of steps\nis the same as for the unordered representation.", - "token_count": 296, - "has_code": true, + "content": "(In the worst case, the object turns out\nnot to be in the set.) Hence, if the set has\n$n$ elements,\nis_element_of_set\nmight take up to $n$ steps.\n\nThe number\nof steps required by\nadjoin_set,\nwhich uses\nthis operation, also grows as $\\Theta(n)$.\n\nFor\nintersection_set,\nwhich does an\nis_element_of_set\ncheck for each element of , the number of\nsteps required grows as the product of the sizes of the sets involved, or\n$\\Theta(n^{2})$ for two sets of size\n$n$.\n\nThe same will be true of\nunion_set.\n\nOne way to speed up our set operations is to change the representation\nso that the set elements are listed in increasing order.\n\nTo do this,\nwe need some way to compare two objects so that we can say which is\nbigger.\n\nFor example, we could compare\nstrings\nlexicographically, or\nwe could agree on some method for assigning a unique number to an\nobject and then compare the elements by comparing the corresponding\nnumbers.\n\nTo keep our discussion simple, we will consider only the\ncase where the set elements are numbers, so that we can compare\nelements using and.\n\nWe will represent a set of\nnumbers by listing its elements in increasing order.\n\nWhereas our\nfirst representation above allowed us to represent the set\n$\\{1,3,6,10\\}$ by listing the elements in any\norder, our new representation allows only the list\nlist(1, 3, 6, 10).\n\nOne advantage of ordering shows up in\nis_element_of_set:\nIn checking for the presence of an item, we no longer have to scan the\nentire set.\n\nIf we reach a set element that is larger than the item we\nare looking for, then we know that the item is not in the set:", + "token_count": 281, + "has_code": false, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", "subsection": "Example: Representing Sets", @@ -780,8 +790,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_3" }, { - "content": "In the worst case, the item we are\nlooking for may be the largest one in the set, so the number of steps\nis the same as for the unordered representation.\n\nOn the other hand,\nif we search for items of many different sizes we can expect that\nsometimes we will be able to stop searching at a point near the\nbeginning of the list and that other times we will still need to\nexamine most of the list.\n\nOn the average we should expect to have to\nexamine about half of the items in the set.\n\nThus, the average\nnumber of steps required will be about $n/2$.\n\nThis is still $\\Theta(n)$ growth, but\nit does save us, on the average, a factor of 2 in number of steps over the\nprevious implementation.\n\nWe obtain a more impressive speedup with\nintersection_set.\n\nIn the unordered representation this operation required\n$\\Theta(n^2)$ steps, because we performed a\ncomplete scan of\ntails\nof the two sets.\n\nSuppose, however, that\ntail\nof\ntail\nof\nfunction:\n\n```javascript\nintersection_set_ordered\n intersection_set_example2\n [ 10, [ 20, null ] ]\n\nfunction intersection_set(set1, set2) {\n if (is_null(set1) || is_null(set2)) {\n return null;\n } else {\n const x1 = head(set1);\n const x2 = head(set2);\n return x1 === x2\n ? pair(x1, intersection_set(tail(set1), tail(set2)))\n : x1 < x2\n ? intersection_set(tail(set1), set2)\n : // $\\texttt{x2 < x1}$\n\t intersection_set(set1, tail(set2));\n }\n}\n```\n\n```javascript\nintersection_set_example2\n intersection_set_ordered\n\nintersection_set(\n list(10, 20, 30),\n list(10, 15, 20));\n```\n\nTo estimate the number of steps required by this process, observe that at each step we reduce the intersection problem to computing intersections of smaller\n\nsets removing the first element from $\\Theta(n)$ growth rather than $\\Theta(n^2)$ a considerable speedup, even for sets of moderate size.\n\nWe can do better than the ordered-list representation by arranging the set\nelements in the form of a tree.", - "token_count": 302, + "content": "If we reach a set element that is larger than the item we\nare looking for, then we know that the item is not in the set:\n\n```javascript\nis_element_of_set_example_2\n\nis_element_of_set(15, list(10, 15, 20));\n```\n\nHow many steps does this save?\n\nIn the worst case, the item we are\nlooking for may be the largest one in the set, so the number of steps\nis the same as for the unordered representation.\n\nOn the other hand,\nif we search for items of many different sizes we can expect that\nsometimes we will be able to stop searching at a point near the\nbeginning of the list and that other times we will still need to\nexamine most of the list.\n\nOn the average we should expect to have to\nexamine about half of the items in the set.\n\nThus, the average\nnumber of steps required will be about $n/2$.\n\nThis is still $\\Theta(n)$ growth, but\nit does save us, on the average, a factor of 2 in number of steps over the\nprevious implementation.\n\nWe obtain a more impressive speedup with\nintersection_set.\n\nIn the unordered representation this operation required\n$\\Theta(n^2)$ steps, because we performed a\ncomplete scan of for each element of.\n\nBut with the ordered representation,\nwe can use a more clever method.\n\nBegin by comparing the initial elements,\nand\n, of the two sets.\n\nIf\nequals\n, then that gives an element of the\nintersection, and the rest of the intersection is the intersection of the\ntails\nof the two sets.\n\nSuppose, however, that\nis less than.\n\nSince\nis the smallest element in\n, we can immediately conclude that\ncannot appear anywhere in\nand hence is not in the intersection.\n\nHence, the intersection is equal to the intersection of\nwith the\ntail\nof.", + "token_count": 294, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", @@ -790,9 +800,9 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_4" }, { - "content": "We can do better than the ordered-list representation by arranging the set\nelements in the form of a tree.\n\nEach node of the tree holds one element of\nthe set, called the entry at that node, and a link to each\nof two other (possibly empty) nodes.\n\nThe left link points to\nelements smaller than the one at the node, and the right\nlink to elements greater than the one at the node.\n\nFigure shows some trees that represent\nthe set $\\{1,3,5,7,9,11\\}$.\n\nThe same set may be\nrepresented by a tree in a number of different ways.\n\nThe only thing we\nrequire for a valid representation is that all elements in the left subtree\nbe smaller than the node entry and that all elements in the right subtree be\nlarger.\n\nVarious binary trees that represent the set\n$\\{ 1,3,5,7,9,11 \\}$.\n\nThe advantage of the tree representation is this: Suppose we want to check\nwhether a number $x$ is contained in a set.\n\nWe\nbegin by comparing $x$ with the entry in the\ntop node.\n\nIf $x$ is less than this, we know\nthat we need only search the left subtree; if $x$\nis greater, we need only search the right subtree.\n\nNow, if the tree is\nbalanced, each of these subtrees will be about half the size\nof the original.\n\nThus, in one step we have reduced the problem of\nsearching a tree of size $n$ to searching a tree\nof size $n/2$.\n\nSince the size of the tree is\nhalved at each step, we should expect that the number of steps needed to\nsearch a tree of size $n$ grows as\n$\\Theta(\\log n)$.\n\nWe can represent trees by using functions :", - "token_count": 282, - "has_code": false, + "content": "Hence, the intersection is equal to the intersection of\nwith the\ntail\nof.\n\nHere is the\nfunction:\n\n```javascript\nintersection_set_ordered\n intersection_set_example2\n [ 10, [ 20, null ] ]\n\nfunction intersection_set(set1, set2) {\n if (is_null(set1) || is_null(set2)) {\n return null;\n } else {\n const x1 = head(set1);\n const x2 = head(set2);\n return x1 === x2\n ? pair(x1, intersection_set(tail(set1), tail(set2)))\n : x1 < x2\n ? intersection_set(tail(set1), set2)\n : // $\\texttt{x2 < x1}$\n\t intersection_set(set1, tail(set2));\n }\n}\n```\n\n```javascript\nintersection_set_example2\n intersection_set_ordered\n\nintersection_set(\n list(10, 20, 30),\n list(10, 15, 20));\n```\n\nTo estimate the number of steps required by this process, observe that at\neach step we reduce the intersection problem to computing intersections of\nsmaller sets removing the first element from\nor\nor both.\n\nThus, the number of steps required is at most the sum of the sizes\nof and ,\nrather than the product of the sizes as with the unordered representation.\n\nThis is $\\Theta(n)$ growth rather than\n$\\Theta(n^2)$ a considerable speedup,\neven for sets of moderate size.\n\nWe can do better than the ordered-list representation by arranging the set\nelements in the form of a tree.\n\nEach node of the tree holds one element of\nthe set, called the entry at that node, and a link to each\nof two other (possibly empty) nodes.\n\nThe left link points to\nelements smaller than the one at the node, and the right\nlink to elements greater than the one at the node.\n\nFigure shows some trees that represent\nthe set $\\{1,3,5,7,9,11\\}$.\n\nThe same set may be\nrepresented by a tree in a number of different ways.\n\nThe only thing we\nrequire for a valid representation is that all elements in the left subtree\nbe smaller than the node entry and that all elements in the right subtree be\nlarger.\n\nVarious binary trees that represent the set\n$\\{ 1,3,5,7,9,11 \\}$.", + "token_count": 304, + "has_code": true, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", "subsection": "Example: Representing Sets", @@ -800,8 +810,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_5" }, { - "content": "We can represent trees by using functions :\n\n```javascript\nmake_tree_function\n make_tree_example\n 20\n\nfunction entry(tree) { return head(tree); }\n\nfunction left_branch(tree) { return head(tail(tree)); }\n\nfunction right_branch(tree) { return head(tail(tail(tree))); }\n\nfunction make_tree(entry, left, right) {\n return list(entry, left, right);\n}\n```\n\n```javascript\nmake_tree_example\n\nentry(\n left_branch(\n right_branch(\n make_tree(10,\n null,\n make_tree(30,\n make_tree(20, null, null),\n null)))));\n```\n\nNow we can write is_element_of_set using the strategy described above:\n\n```javascript\nmake_tree_function\n is_element_of_set_example_3\n true\n\nfunction is_element_of_set(x, set) {\n return is_null(set)\n ? false\n : x === entry(set)\n ? true\n : x < entry(set)\n ? is_element_of_set(x, left_branch(set))\n : // $\\texttt{x > entry(set)}$\n is_element_of_set(x, right_branch(set));\n}\n```\n\n```javascript\nis_element_of_set_example_3\n\nis_element_of_set(20,\n make_tree(10,\n null,\n make_tree(30,\n make_tree(20, null, null),\n null)));\n```\n\nAdjoining an item to a set is implemented similarly and also requires\n$\\Theta(\\log n)$ steps.\n\nTo adjoin an item\nfunction:\n\n```javascript\nadjoin_set_example_2\n\nadjoin_set(10, adjoin_set(15, adjoin_set(20, null)));\n\nhead(tail(head(tail(adjoin_set(10, adjoin_set(15, adjoin_set(20, null)))))));\n```\n\n```javascript\nadjoin_set2\n make_tree_function\n adjoin_set_example_2\n [ 10, [ null, [ null, null ] ] ]\n\nfunction adjoin_set(x, set) {\n return is_null(set)\n ? make_tree(x, null, null)\n : x === entry(set)\n ? set\n : x < entry(set)\n ? make_tree(entry(set),\n adjoin_set(x, left_branch(set)),\n right_branch(set))\n : // $\\texttt{x > entry(set)}$\n make_tree(entry(set),\n left_branch(set),\n adjoin_set(x, right_branch(set)));\n}\n```\n\nThe above claim that searching the tree can be performed in a logarithmic\nnumber of steps rests on the assumption that the tree is\nbalanced, i.e., that the\nleft and the right subtree of every tree have approximately the same\nnumber of elements, so that each subtree contains about half the\nelements of its parent.\n\nBut how can we be certain that the trees we\nconstruct will be balanced?\n\nEven if we start with a balanced tree,\nadding elements with\nadjoin_set\nmay produce an unbalanced result.", - "token_count": 276, + "content": "Various binary trees that represent the set\n$\\{ 1,3,5,7,9,11 \\}$.\n\nWe\nbegin by comparing $x$ with the entry in the\ntop node.\n\nIf $x$ is less than this, we know\nthat we need only search the left subtree; if $x$\nis greater, we need only search the right subtree.\n\nNow, if the tree is\nbalanced, each of these subtrees will be about half the size\nof the original.\n\nThus, in one step we have reduced the problem of\nsearching a tree of size $n$ to searching a tree\nof size $n/2$.\n\nSince the size of the tree is\nhalved at each step, we should expect that the number of steps needed to\nsearch a tree of size $n$ grows as\n$\\Theta(\\log n)$.\n\nFor\nlarge sets, this will be a significant speedup over the previous\nrepresentations.\n\nWe can represent trees by using\nlists.\n\nEach node will be a list of\nthree items: the entry at the node, the left subtree, and the right\nsubtree.\n\nA left or a right subtree of the empty list will indicate\nthat there is no subtree connected there.\n\nWe can describe this\nrepresentation by the following\nfunctions :\n\n```javascript\nmake_tree_function\n make_tree_example\n 20\n\nfunction entry(tree) { return head(tree); }\n\nfunction left_branch(tree) { return head(tail(tree)); }\n\nfunction right_branch(tree) { return head(tail(tail(tree))); }\n\nfunction make_tree(entry, left, right) {\n return list(entry, left, right);\n}\n```\n\n```javascript\nmake_tree_example\n\nentry(\n left_branch(\n right_branch(\n make_tree(10,\n null,\n make_tree(30,\n make_tree(20, null, null),\n null)))));\n```\n\nNow we can write is_element_of_set using the strategy described above:\n\n```javascript\nmake_tree_function\n is_element_of_set_example_3\n true\n\nfunction is_element_of_set(x, set) {\n return is_null(set)\n ? false\n : x === entry(set)\n ? true\n : x < entry(set)\n ? is_element_of_set(x, left_branch(set))\n : // $\\texttt{x > entry(set)}$\n is_element_of_set(x, right_branch(set));\n}\n```\n\n```javascript\nis_element_of_set_example_3\n\nis_element_of_set(20,\n make_tree(10,\n null,\n make_tree(30,\n make_tree(20, null, null),\n null)));\n```\n\nAdjoining an item to a set is implemented similarly and also requires\n$\\Theta(\\log n)$ steps.", + "token_count": 308, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", @@ -810,9 +820,9 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_6" }, { - "content": "Even if we start with a balanced tree,\nadding elements with\nadjoin_set\nmay produce an unbalanced result.\n\nSince the position of a newly adjoined\nelement depends on how the element compares with the items already in the\nset, we can expect that if we add elements randomly the tree\nwill tend to be balanced on the average.\n\nBut this is not a guarantee.\n\nFor\nexample, if we start with an empty set and adjoin the numbers 1 through 7\nin sequence we end up with the highly unbalanced tree shown in\nfigure.\n\nIn this tree all the left\nsubtrees are empty, so it has no advantage over a simple ordered list.\n\nOne\nway to solve this problem is to define an operation that transforms an\narbitrary tree into a balanced tree with the same elements.\n\nThen we can perform this transformation after every few\nadjoin_set\noperations to keep our set in balance.\n\nThere are also other ways to solve\nthis problem, most of which involve designing new data structures for which\nsearching and insertion both can be done in\n$\\Theta(\\log n)$\nsteps.\n\nWe have examined options for using lists to represent sets and have\nseen how the choice of representation for a data object can have a\nlarge impact on the performance of the programs that use the data.\n\nAnother reason for concentrating on sets is that the techniques\ndiscussed here appear again and again in applications involving\ninformation retrieval.\n\nkey.\n\nA key can be anything that uniquely identifies the\nrecord.\n\nFor a personnel file, it might be an employee s ID number.\n\nFor an accounting system, it might be a transaction number.\n\nWhatever\nthe key is, when we define the record as a data structure we should\ninclude a\nfunction\nthat retrieves the key associated with a given record.", - "token_count": 300, - "has_code": false, + "content": "Adjoining an item to a set is implemented similarly and also requires\n$\\Theta(\\log n)$ steps.\n\nIf is equal to the entry, we just\nreturn the node.\n\nIf we are asked to adjoin\nto an empty tree, we generate a tree that\nhas as the entry and empty right and left\nbranches.\n\nHere is the\nfunction:\n\n```javascript\nadjoin_set_example_2\n\nadjoin_set(10, adjoin_set(15, adjoin_set(20, null)));\n\nhead(tail(head(tail(adjoin_set(10, adjoin_set(15, adjoin_set(20, null)))))));\n```\n\n```javascript\nadjoin_set2\n make_tree_function\n adjoin_set_example_2\n [ 10, [ null, [ null, null ] ] ]\n\nfunction adjoin_set(x, set) {\n return is_null(set)\n ? make_tree(x, null, null)\n : x === entry(set)\n ? set\n : x < entry(set)\n ? make_tree(entry(set),\n adjoin_set(x, left_branch(set)),\n right_branch(set))\n : // $\\texttt{x > entry(set)}$\n make_tree(entry(set),\n left_branch(set),\n adjoin_set(x, right_branch(set)));\n}\n```\n\nThe above claim that searching the tree can be performed in a logarithmic\nnumber of steps rests on the assumption that the tree is\nbalanced, i.e., that the\nleft and the right subtree of every tree have approximately the same\nnumber of elements, so that each subtree contains about half the\nelements of its parent.\n\nBut how can we be certain that the trees we\nconstruct will be balanced?\n\nEven if we start with a balanced tree,\nadding elements with\nadjoin_set\nmay produce an unbalanced result.\n\nSince the position of a newly adjoined\nelement depends on how the element compares with the items already in the\nset, we can expect that if we add elements randomly the tree\nwill tend to be balanced on the average.\n\nBut this is not a guarantee.\n\nFor\nexample, if we start with an empty set and adjoin the numbers 1 through 7\nin sequence we end up with the highly unbalanced tree shown in\nfigure.\n\nIn this tree all the left\nsubtrees are empty, so it has no advantage over a simple ordered list.", + "token_count": 296, + "has_code": true, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", "subsection": "Example: Representing Sets", @@ -820,9 +830,9 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_7" }, { - "content": "Whatever\nthe key is, when we define the record as a data structure we should\ninclude a\nfunction\nthat retrieves the key associated with a given record.\n\nNow we represent the data base as a set of records.\n\nTo locate the record\nwith a given key we use a\nfunction\nThe function lookup\nis implemented in almost the same way as\nis_element_of_set.\n\nFor example, if the set of records is implemented as an unordered list, we\ncould use\n\n```javascript\nrecord\n\nfunction make_record(key, data) {\n return pair(key, data);\n}\nfunction key(record) {\n return head(record);\n}\nfunction data(record) {\n return tail(record);\n}\n```\n\n```javascript\nrecord\n lookup_example\n [ 3, 'Earth' ]\n\nfunction lookup(given_key, set_of_records) {\n return is_null(set_of_records)\n ? false\n : equal(given_key, key(head(set_of_records)))\n ? head(set_of_records)\n : lookup(given_key, tail(set_of_records));\n}\n```\n\n```javascript\nlookup_example\n\nlookup(3, list(make_record(2, \"Venus\"),\n make_record(5, \"Jupiter\"),\n make_record(4, \"Mars\"),\n make_record(3, \"Earth\"),\n make_record(6, \"Saturn\")));\n```\n\nOf course, there are better ways to represent large sets than as unordered\nlists.\n\nInformation-retrieval systems in which records have to be\nrandomly accessed are typically implemented by a tree-based\nmethod, such as the binary-tree representation discussed previously.\n\nIn designing such a system the methodology of data abstraction\ncan be a great help.\n\nThe designer can create an initial implementation\nusing a simple, straightforward representation such as unordered lists.\n\nThis will be unsuitable for the eventual system, but it can be useful in\nproviding a quick and dirty data base with which to test the\nrest of the system.\n\nLater on, the data representation can be modified to\nbe more sophisticated.\n\nIf the data base is accessed in terms of abstract\nselectors and constructors, this change in representation will not require\nany changes to the rest of the system.", - "token_count": 279, - "has_code": true, + "content": "In this tree all the left\nsubtrees are empty, so it has no advantage over a simple ordered list.\n\nThen we can perform this transformation after every few\nadjoin_set\noperations to keep our set in balance.\n\nThere are also other ways to solve\nthis problem, most of which involve designing new data structures for which\nsearching and insertion both can be done in\n$\\Theta(\\log n)$\nsteps.\n\nWe have examined options for using lists to represent sets and have\nseen how the choice of representation for a data object can have a\nlarge impact on the performance of the programs that use the data.\n\nAnother reason for concentrating on sets is that the techniques\ndiscussed here appear again and again in applications involving\ninformation retrieval.\n\nConsider a data base containing a large number of individual records,\nsuch as the personnel files for a company or the transactions in an\naccounting system.\n\nA typical data-management system spends a large\namount of time accessing or modifying the data in the records and\ntherefore requires an efficient method for accessing records.\n\nThis is\ndone by identifying a part of each record to serve as an identifying\nkey.\n\nA key can be anything that uniquely identifies the\nrecord.\n\nFor a personnel file, it might be an employee s ID number.\n\nFor an accounting system, it might be a transaction number.\n\nWhatever\nthe key is, when we define the record as a data structure we should\ninclude a\nselector\nfunction\nthat retrieves the key associated with a given record.\n\nNow we represent the data base as a set of records.", + "token_count": 264, + "has_code": false, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", "subsection": "Example: Representing Sets", @@ -830,8 +840,18 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_8" }, { - "content": "So far, we have used strings in order to display messages,\nusing the functions display and\nerror (as for example in\nexercise ).\n\nWe can form compound data using strings and have lists such as\n\n```javascript\nlist(\"a\", \"b\", \"c\", \"d\")\nlist(23, 45, 17)\nlist(list(\"Jakob\", 27), list(\"Lova\", 9), list(\"Luisa\", 24))\n```\n\nIn order to distinguish strings from names, we surround them z denotes the value of the name z , whereas the JavaScript expression \"z\" denotes\n\na string that consists of a single character, namely the last letter in the English alphabet in lower case.\n\nVia quotation marks, we can distinguish between strings and names:\n\n```javascript\nab\n\nconst a = 1;\nconst b = 2;\n```\n\n```javascript\nlist_ab\n ab\n\t [ 1, [ 2, null ] ]\n\nlist(a, b);\n```\n\n```javascript\nlist_quote_a_quote_b\n\t [ 'a', [ 'b', null ] ]\n\nlist(\"a\", \"b\");\n```\n\n```javascript\nlist_quote_a_b\n ab\n\t [ 'a', [ 2, null ] ]\n\nlist(\"a\", b);\n```\n\nIn section , we introduced\n=== and\n!==\nas primitive predicates on numbers.\n=== and\n!==.\n\nThe predicate\n===\nreturns true if and only\nif the two strings are the same, and\n!==\nreturns true if and only\nif the two strings are not the same. === , we can implement\na useful function called member.\n\nThis takes two arguments: a string and a list of strings or\na number and a list of numbers.\n\nIf the first argument is\nnot contained in the list (i.e., is not\n=== to any item in the list),\nthen member returns\nnull.\n\nOtherwise, it returns the\nsublist of the list beginning with the first occurrence of the\nstring or number:\n\n```javascript\nmemq\n memq_example\n\t null\n\nfunction member(item, x) {\n return is_null(x)\n ? null\n : item === head(x)\n ? x\n : member(item, tail(x));\n}\n```\n\nFor example, the value of\n\n```javascript\nmemq_example\n memq\n\nmember(\"apple\", list(\"pear\", \"banana\", \"prune\"))\n\nmember(\"apple\", list(\"pear\", \"banana\", \"prune\"));\n```", - "token_count": 310, + "content": "Now we represent the data base as a set of records.\n\nThe function lookup\nis implemented in almost the same way as\nis_element_of_set.\n\nFor example, if the set of records is implemented as an unordered list, we\ncould use\n\n```javascript\nrecord\n\nfunction make_record(key, data) {\n return pair(key, data);\n}\nfunction key(record) {\n return head(record);\n}\nfunction data(record) {\n return tail(record);\n}\n```\n\n```javascript\nrecord\n lookup_example\n [ 3, 'Earth' ]\n\nfunction lookup(given_key, set_of_records) {\n return is_null(set_of_records)\n ? false\n : equal(given_key, key(head(set_of_records)))\n ? head(set_of_records)\n : lookup(given_key, tail(set_of_records));\n}\n```\n\n```javascript\nlookup_example\n\nlookup(3, list(make_record(2, \"Venus\"),\n make_record(5, \"Jupiter\"),\n make_record(4, \"Mars\"),\n make_record(3, \"Earth\"),\n make_record(6, \"Saturn\")));\n```\n\nOf course, there are better ways to represent large sets than as unordered\nlists.\n\nInformation-retrieval systems in which records have to be\nrandomly accessed are typically implemented by a tree-based\nmethod, such as the binary-tree representation discussed previously.\n\nIn designing such a system the methodology of data abstraction\ncan be a great help.\n\nThe designer can create an initial implementation\nusing a simple, straightforward representation such as unordered lists.\n\nThis will be unsuitable for the eventual system, but it can be useful in\nproviding a quick and dirty data base with which to test the\nrest of the system.\n\nLater on, the data representation can be modified to\nbe more sophisticated.\n\nIf the data base is accessed in terms of abstract\nselectors and constructors, this change in representation will not require\nany changes to the rest of the system.", + "token_count": 240, + "has_code": true, + "chapter": "Building Abstractions with Data", + "section": "Symbolic Data", + "subsection": "Example: Representing Sets", + "chunk_index": 9, + "chunk_id": "Building_Abstractions_with_Data_Example_Representing_Sets_9" + }, + { + "content": "So far, we have used strings in order to display messages,\nusing the functions display and\nerror (as for example in\nexercise ).\n\nWe can form compound data using strings and have lists such as\n\n```javascript\nlist(\"a\", \"b\", \"c\", \"d\")\nlist(23, 45, 17)\nlist(list(\"Jakob\", 27), list(\"Lova\", 9), list(\"Luisa\", 24))\n```\n\nIn order to distinguish strings from names, we surround them\nwith double quotation marks.\n\nFor example, the JavaScript expression\nz denotes the value of the\nname z , whereas the JavaScript\nexpression \"z\" denotes a string\nthat consists of a single character, namely the last letter in the\nEnglish alphabet in lower case.\n\nVia quotation marks, we can distinguish between strings and names:\n\n```javascript\nab\n\nconst a = 1;\nconst b = 2;\n```\n\n```javascript\nlist_ab\n ab\n\t [ 1, [ 2, null ] ]\n\nlist(a, b);\n\n[1, [2, null]]\n```\n\n```javascript\nlist_quote_a_quote_b\n\t [ 'a', [ 'b', null ] ]\n\nlist(\"a\", \"b\");\n\n[\"a\", [\"b\", null]]\n```\n\n```javascript\nlist_quote_a_b\n ab\n\t [ 'a', [ 2, null ] ]\n\nlist(\"a\", b);\n\n[\"a\", [2, null]]\n```\n\nIn section , we introduced\n=== and\n!==\nas primitive predicates on numbers.\n\nFrom now\non, we shall allow two\nstrings as operands of\n=== and\n!==.\n\nThe predicate\n===\nreturns true if and only\nif the two strings are the same, and\n!==\nreturns true if and only\nif the two strings are not the same.\n\nUsing === , we can implement\na useful function called member.\n\nThis takes two arguments: a string and a list of strings or\na number and a list of numbers.\n\nIf the first argument is\nnot contained in the list (i.e., is not\n=== to any item in the list),\nthen member returns\nnull.\n\nOtherwise, it returns the\nsublist of the list beginning with the first occurrence of the\nstring or number:", + "token_count": 300, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", @@ -840,8 +860,8 @@ "chunk_id": "Building_Abstractions_with_Data_Strings_1" }, { - "content": "For example, the value of\n\nis null , whereas the value of\n\n```javascript\nmemq\n\t [ 'apple', [ 'pear', null ] ]\n\nmember(\"apple\", list(\"x\", \"y\", \"apple\", \"pear\"))\n\nmember(\"apple\", list(\"x\", \"y\", \"apple\", \"pear\"));\n```\n\nis list(\"apple\", \"pear\").", - "token_count": 35, + "content": "Otherwise, it returns the\nsublist of the list beginning with the first occurrence of the\nstring or number:\n\nFor example, the value of\n\n```javascript\nmemq_example\n memq\n\nmember(\"apple\", list(\"pear\", \"banana\", \"prune\"))\n\nmember(\"apple\", list(\"pear\", \"banana\", \"prune\"));\n```\n\nis null , whereas the value of\n\n```javascript\nmemq\n\t [ 'apple', [ 'pear', null ] ]\n\nmember(\"apple\", list(\"x\", \"y\", \"apple\", \"pear\"))\n\nmember(\"apple\", list(\"x\", \"y\", \"apple\", \"pear\"));\n```\n\nis list(\"apple\", \"pear\").", + "token_count": 65, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Symbolic Data", @@ -850,8 +870,8 @@ "chunk_id": "Building_Abstractions_with_Data_Strings_2" }, { - "content": "In the previous section, we saw how to design systems in which data\nobjects can be represented in more than one way.\n\nThe key idea is to\nlink the code that specifies the data operations to the several\nrepresentations by means of generic interface\nfunctions.\n\nNow we will see how to use this same idea not only to define operations\nthat are generic over different representations but also to define\noperations that are\n(add_rat,\nsub_rat,\nmul_rat,\ndiv_rat)\nof section , and the complex-number\narithmetic that we implemented in\nsection.\n\nWe will now use\ndata-directed techniques to construct a package of arithmetic operations\nthat incorporates all the arithmetic packages we have already constructed.\n\nFigure\nshows the structure of the system we\nshall build.\n\nNotice the\nnumbers, there is a single\nfunction\nThe function add\nis part of a generic interface that allows the separate ordinary-arithmetic,\nrational-arithmetic, and complex-arithmetic packages to be accessed\nuniformly by programs that use numbers.\n\nAny individual arithmetic package\n(such as the complex package) may itself be accessed through generic\nfunctions\n(such as\nadd_complex)\nthat combine packages designed for different representations (such as\nrectangular and polar).\n\nMoreover, the structure of the system is additive,\nso that one can design the individual arithmetic packages separately and\ncombine them to produce a generic arithmetic system.\n\nGeneric", - "token_count": 216, + "content": "In the previous section, we saw how to design systems in which data\nobjects can be represented in more than one way.\n\nThe key idea is to\nlink the code that specifies the data operations to the several\nrepresentations by means of generic interface\nfunctions.\n\nNow we will see how to use this same idea not only to define operations\nthat are generic over different representations but also to define\noperations that are\ngeneric over different kinds of arguments.\n\nWe have\nalready seen several different packages of arithmetic operations: the\nprimitive arithmetic (,\n, ,\n) built into our language, the\nrational-number arithmetic\n(add_rat,\nsub_rat,\nmul_rat,\ndiv_rat)\nof section , and the complex-number\narithmetic that we implemented in\nsection.\n\nWe will now use\ndata-directed techniques to construct a package of arithmetic operations\nthat incorporates all the arithmetic packages we have already constructed.\n\nFigure\nshows the structure of the system we\nshall build.\n\nNotice the\nabstraction barriers.\n\nFrom the perspective\nof someone using numbers, there is a single\nfunction\nthat operates on whatever numbers are\nsupplied.\n\nThe function add\nis part of a generic interface that allows the separate ordinary-arithmetic,\nrational-arithmetic, and complex-arithmetic packages to be accessed\nuniformly by programs that use numbers.\n\nAny individual arithmetic package\n(such as the complex package) may itself be accessed through generic\nfunctions\n(such as\nadd_complex)\nthat combine packages designed for different representations (such as\nrectangular and polar).\n\nMoreover, the structure of the system is additive,\nso that one can design the individual arithmetic packages separately and\ncombine them to produce a generic arithmetic system.\n\nGeneric arithmetic system.", + "token_count": 263, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -860,8 +880,8 @@ "chunk_id": "Building_Abstractions_with_Data_Systems_with_Generic_Operations_1" }, { - "content": "We have seen how to define a unified arithmetic system that\nencompasses ordinary numbers, complex numbers, rational numbers, and\nany other type of number we might decide to invent, but we have\nignored an important issue.\n\nThe operations we have defined so far\ntreat the different data types as being completely independent.\n\nThus,\nthere are separate packages for adding, say, two ordinary numbers, or\ntwo complex numbers.\n\nWhat we have not yet considered is the fact that\nit is meaningful to define operations that cross the type boundaries,\nsuch as the addition of a complex number to an ordinary number.\n\nWe\nhave gone to great pains to introduce barriers between parts of our\nprograms so that they can be developed and understood separately.\n\nWe\nwould like to introduce the cross-type operations in some carefully\ncontrolled way, so that we can support them\nwithout seriously violating our module boundaries.\n\nOne way to handle\nfunction\nfor each possible combination of types for which the operation is valid.\n\nFor example, we could extend the complex-number package so that it\nprovides a\nfunction\nfor adding complex numbers to ordinary numbers and installs this in the\ntable using the tag\nlist(\"complex\", \"javascript_number\") :\n\n```javascript\nadd_complex_to_javascript_number_example\n\nconst c = make_complex_from_real_imag(4, 3);\nconst n = make_javascript_number(7);\n\nadd(c, n);\n```\n\n```javascript\nadd_complex_to_javascript_number\n install_javascript_number_package_usage\n install_complex_package_usage\n add_complex_to_javascript_number_example\n [ 'complex', [ 'rectangular', [ 11, 3 ] ] ]\n\n// to be included in the complex package\nfunction add_complex_to_javascript_num(z, x) {\n return make_complex_from_real_imag(real_part(z) + x, imag_part(z));\n}\nput(\"add\", list(\"complex\", \"javascript_number\"),\n (z, x) => tag(add_complex_to_javascript_num(z, x)));\n```\n\nThis technique works, but it is cumbersome.\n\nWith such a system, the\ncost of introducing a new type is not just the construction of the\npackage of\nfunctions\nfor that type but also the construction and installation of the\nfunctions\nthat implement the cross-type operations.", - "token_count": 299, + "content": "We have seen how to define a unified arithmetic system that\nencompasses ordinary numbers, complex numbers, rational numbers, and\nany other type of number we might decide to invent, but we have\nignored an important issue.\n\nThe operations we have defined so far\ntreat the different data types as being completely independent.\n\nThus,\nthere are separate packages for adding, say, two ordinary numbers, or\ntwo complex numbers.\n\nWhat we have not yet considered is the fact that\nit is meaningful to define operations that cross the type boundaries,\nsuch as the addition of a complex number to an ordinary number.\n\nWe\nhave gone to great pains to introduce barriers between parts of our\nprograms so that they can be developed and understood separately.\n\nWe\nwould like to introduce the cross-type operations in some carefully\ncontrolled way, so that we can support them\nwithout seriously violating our module boundaries.\n\nOne way to handle\ncross-type operations is to design a different\nfunction\nfor each possible combination of types for which the operation is valid.\n\nFor example, we could extend the complex-number package so that it\nprovides a\nfunction\nfor adding complex numbers to ordinary numbers and installs this in the\ntable using the tag\nlist(\"complex\", \"javascript_number\") :\n\n```javascript\nadd_complex_to_javascript_number_example\n\nconst c = make_complex_from_real_imag(4, 3);\nconst n = make_javascript_number(7);\n\nadd(c, n);\n```\n\n```javascript\nadd_complex_to_javascript_number\n install_javascript_number_package_usage\n install_complex_package_usage\n add_complex_to_javascript_number_example\n [ 'complex', [ 'rectangular', [ 11, 3 ] ] ]\n\n// to be included in the complex package\nfunction add_complex_to_javascript_num(z, x) {\n return make_complex_from_real_imag(real_part(z) + x, imag_part(z));\n}\nput(\"add\", list(\"complex\", \"javascript_number\"),\n (z, x) => tag(add_complex_to_javascript_num(z, x)));\n```\n\nThis technique works, but it is cumbersome.", + "token_count": 268, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -870,8 +890,8 @@ "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_1" }, { - "content": "With such a system, the\ncost of introducing a new type is not just the construction of the\npackage of\nfunctions\nfor that type but also the construction and installation of the\nfunctions\nthat implement the cross-type operations.\n\nThis can easily be much more\ncode than is needed to define the operations on the type itself.\n\nThe\nmethod also undermines our ability to combine separate packages additively,\nor least to limit the extent to which the implementors of the individual\npackages need to take account of other packages.\n\nFor instance, in the\nexample above, it seems reasonable that handling mixed operations on\ncomplex numbers and ordinary numbers should be the responsibility of\nthe complex-number package.\n\nCombining rational numbers and complex\nnumbers, however, might be done by the complex package, by the rational\npackage, or by some third package that uses operations extracted from\nthese two packages.\n\nFormulating coherent policies on the division of\nresponsibility among packages can be an overwhelming task in designing\nsystems with many packages and many cross-type operations.\n\nIn the general situation of completely unrelated operations acting on\ncompletely unrelated types, implementing explicit cross-type operations,\ncumbersome though it may be, is the best that one can hope for.\n\nFortunately, we can usually do better by taking advantage of additional\nstructure that may be latent in our type system.\n\nOften the different\ndata types are not completely independent, and there may be ways by which\nobjects of one type may be viewed as being of another type.\n\nThis process\nis called coercion.\n\nFor example, if we are asked to\narithmetically combine an ordinary number with a complex number, we can\nview the ordinary number as a complex number whose imaginary part is zero.", - "token_count": 285, + "content": "This technique works, but it is cumbersome.\n\nThis can easily be much more\ncode than is needed to define the operations on the type itself.\n\nThe\nmethod also undermines our ability to combine separate packages additively,\nor least to limit the extent to which the implementors of the individual\npackages need to take account of other packages.\n\nFor instance, in the\nexample above, it seems reasonable that handling mixed operations on\ncomplex numbers and ordinary numbers should be the responsibility of\nthe complex-number package.\n\nCombining rational numbers and complex\nnumbers, however, might be done by the complex package, by the rational\npackage, or by some third package that uses operations extracted from\nthese two packages.\n\nFormulating coherent policies on the division of\nresponsibility among packages can be an overwhelming task in designing\nsystems with many packages and many cross-type operations.\n\nIn the general situation of completely unrelated operations acting on\ncompletely unrelated types, implementing explicit cross-type operations,\ncumbersome though it may be, is the best that one can hope for.\n\nFortunately, we can usually do better by taking advantage of additional\nstructure that may be latent in our type system.\n\nOften the different\ndata types are not completely independent, and there may be ways by which\nobjects of one type may be viewed as being of another type.\n\nThis process\nis called coercion.\n\nFor example, if we are asked to\narithmetically combine an ordinary number with a complex number, we can\nview the ordinary number as a complex number whose imaginary part is zero.\n\nThis transforms the problem to that of combining two complex numbers, which\ncan be handled in the ordinary way by the complex-arithmetic package.", + "token_count": 277, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -880,8 +900,8 @@ "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_2" }, { - "content": "For example, if we are asked to\narithmetically combine an ordinary number with a complex number, we can\nview the ordinary number as a complex number whose imaginary part is zero.\n\nThis transforms the problem to that of combining two complex numbers, which\ncan be handled in the ordinary way by the complex-arithmetic package.\n\nIn general, we can implement this idea by designing\nfunctions\nthat transform an object of one type into an equivalent\nobject of another type.\n\nHere is a typical coercion\nfunction,\nwhich transforms a given ordinary number to a complex number with that real\npart and zero imaginary part:\n\n```javascript\njavascript_number_to_complex\n\nfunction javascript_number_to_complex(n) {\n return make_complex_from_real_imag(contents(n), 0);\n}\n```\n\nfunctions in a special coercion table, indexed under the names of the two types:\n\n```javascript\nput_get_coercion\n\nlet coercion_list = null;\n\nfunction clear_coercion_list() {\n coercion_list = null;\n}\n\nfunction put_coercion(type1, type2, item) {\n if (is_null(get_coercion(type1, type2))) {\n coercion_list = pair(list(type1, type2, item),\n coercion_list);\n } else {\n return coercion_list;\n }\n}\n\nfunction get_coercion(type1, type2) {\n function get_type1(list_item) {\n return head(list_item);\n }\n function get_type2(list_item) {\n return head(tail(list_item));\n }\n function get_item(list_item) {\n return head(tail(tail(list_item)));\n }\n function get_coercion_iter(items) {\n if (is_null(items)) {\n return undefined;\n } else {\n const top = head(items);\n return equal(type1, get_type1(top)) &&\n equal(type2, get_type2(top))\n ? get_item(top)\n : get_coercion_iter(tail(items));\n }\n }\n return get_coercion_iter(coercion_list);\n}\n```\n\n```javascript\nput_coercion_usage\n put_get_coercion\n javascript_number_to_complex\n install_complex_package_usage\n put_coercion_usage_example\n put_get_coercion\n\nput_coercion(\"javascript_number\", \"complex\",\n javascript_number_to_complex);\n```\n\n```javascript\nput_coercion_usage_example\n\nget_coercion(\"javascript_number\", \"complex\");\n```\n\n(We assume that there are\nput_coercion\nand\nget_coercion\nfunctions\navailable for manipulating this table.) Generally some of the slots in\nthe table will be empty, because it is not generally possible to coerce\nan arbitrary data object of each type into all other types.\n\nFor example,\nthere is no way to coerce an arbitrary complex number to an ordinary\nnumber, so there will be no general\ncomplex_to_javascript_number\nfunction\nincluded in the table.", - "token_count": 302, + "content": "This transforms the problem to that of combining two complex numbers, which\ncan be handled in the ordinary way by the complex-arithmetic package.\n\nHere is a typical coercion\nfunction,\nwhich transforms a given ordinary number to a complex number with that real\npart and zero imaginary part:\n\n```javascript\njavascript_number_to_complex\n\nfunction javascript_number_to_complex(n) {\n return make_complex_from_real_imag(contents(n), 0);\n}\n```\n\nWe install these coercion functions in a special coercion table, indexed under the names of the two types:\n\n```javascript\nput_get_coercion\n\nlet coercion_list = null;\n\nfunction clear_coercion_list() {\n coercion_list = null;\n}\n\nfunction put_coercion(type1, type2, item) {\n if (is_null(get_coercion(type1, type2))) {\n coercion_list = pair(list(type1, type2, item),\n coercion_list);\n } else {\n return coercion_list;\n }\n}\n\nfunction get_coercion(type1, type2) {\n function get_type1(list_item) {\n return head(list_item);\n }\n function get_type2(list_item) {\n return head(tail(list_item));\n }\n function get_item(list_item) {\n return head(tail(tail(list_item)));\n }\n function get_coercion_iter(items) {\n if (is_null(items)) {\n return undefined;\n } else {\n const top = head(items);\n return equal(type1, get_type1(top)) &&\n equal(type2, get_type2(top))\n ? get_item(top)\n : get_coercion_iter(tail(items));\n }\n }\n return get_coercion_iter(coercion_list);\n}\n```\n\n```javascript\nput_coercion_usage\n put_get_coercion\n javascript_number_to_complex\n install_complex_package_usage\n put_coercion_usage_example\n put_get_coercion\n\nput_coercion(\"javascript_number\", \"complex\",\n javascript_number_to_complex);\n```\n\n```javascript\nput_coercion_usage_example\n\nget_coercion(\"javascript_number\", \"complex\");\n```\n\n(We assume that there are\nput_coercion\nand\nget_coercion\nfunctions\navailable for manipulating this table.) Generally some of the slots in\nthe table will be empty, because it is not generally possible to coerce\nan arbitrary data object of each type into all other types.\n\nFor example,\nthere is no way to coerce an arbitrary complex number to an ordinary\nnumber, so there will be no general\ncomplex_to_javascript_number\nfunction\nincluded in the table.\n\nOnce the coercion table has been set up, we can handle coercion in a\nuniform manner by modifying the\napply_generic\nfunction\nof section.\n\nWhen asked to apply an\noperation, we first check whether the operation is defined for the\narguments types, just as before.\n\nIf so, we dispatch to the\nfunction\nfound in the operation-and-type table.", + "token_count": 307, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -890,8 +910,8 @@ "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_3" }, { - "content": "For example,\nthere is no way to coerce an arbitrary complex number to an ordinary\nnumber, so there will be no general\ncomplex_to_javascript_number\nfunction\nincluded in the table.\n\nOnce the coercion table has been set up, we can handle coercion in a\nuniform manner by modifying the\napply_generic\nfunction\nof section.\n\nWhen asked to apply an\noperation, we first check whether the operation is defined for the\narguments types, just as before.\n\nIf so, we dispatch to the\nfunction\nfound in the operation-and-type table.\n\nOtherwise, we try coercion.\n\nFor\nsimplicity, we consider only the case where there are two\narguments.\nfunction:", - "token_count": 101, + "content": "If so, we dispatch to the\nfunction\nfound in the operation-and-type table.\n\nFor\nsimplicity, we consider only the case where there are two\narguments.\n\nWe check the coercion table to see if objects\nof the first type can be coerced to the second type.\n\nIf so, we coerce the\nfirst argument and try the operation again.\n\nIf objects of the first type\ncannot in general be coerced to the second type, we try the coercion the\nother way around to see if there is a way to coerce the second argument to\nthe type of the first argument.\n\nFinally, if there is no known way to coerce\neither type to the other type, we give up.\n\nHere is the\nfunction:", + "token_count": 120, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -900,8 +920,8 @@ "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_4" }, { - "content": "For\nsimplicity, we consider only the case where there are two\narguments.\nfunction:\n\n```javascript\nbase_operation_table\n\n// operation_table, put and get\n// from chapter 3 (section 3.3.3)\nfunction assoc(key, records) {\n return is_null(records)\n ? undefined\n : equal(key, head(head(records)))\n ? head(records)\n : assoc(key, tail(records));\n}\nfunction make_table() {\n const local_table = list(\"*table*\");\n function lookup(key_1, key_2) {\n const subtable = assoc(key_1, tail(local_table));\n if (is_undefined(subtable)) {\n return undefined;\n } else {\n const record = assoc(key_2, tail(subtable));\n if (is_undefined(record)) {\n return undefined;\n } else {\n return tail(record);\n }\n }\n }\n function insert(key_1, key_2, value) {\n const subtable = assoc(key_1, tail(local_table));\n if (is_undefined(subtable)) {\n set_tail(local_table,\n pair(list(key_1, pair(key_2, value)),\n tail(local_table)));\n } else {\n const record = assoc(key_2, tail(subtable));\n if (is_undefined(record)) {\n set_tail(subtable,\n pair(pair(key_2, value),\n tail(subtable)));\n } else {\n set_tail(record, value);\n }\n }\n }\n function dispatch(m) {\n return m === \"lookup\"\n ? lookup\n : m === \"insert\"\n ? insert\n : \"undefined operation -- table\";\n }\n return dispatch;\n}\nconst operation_table = make_table();\nconst get = operation_table(\"lookup\");\nconst put = operation_table(\"insert\");\n\n// In Source, most functions have a fixed number of arguments.\n// (The function list is the only exception, to this so far.)\n// The function apply_in_underlying_javascript allows us to\n// apply any given function fun to all elements of the argument\n// list args, as if they were separate arguments\nfunction apply(fun, args) {\n return apply_in_underlying_javascript(fun, args);\n}\nfunction add(x, y) {\n return apply_generic(\"add\", list(x, y));\n}\nfunction sub(x, y) {\n return apply_generic(\"sub\", list(x, y));\n}\nfunction mul(x, y) {\n return apply_generic(\"mul\", list(x, y));\n}\nfunction div(x, y) {\n return apply_generic(\"div\", list(x, y));\n}\n\nfunction attach_tag(type_tag, contents) {\n return pair(type_tag, contents);\n}\nfunction type_tag(datum) {\n return is_pair(datum)\n ? head(datum)\n : error(datum, \"bad tagged datum -- type_tag\");\n}\nfunction contents(datum) {\n return is_pair(datum)\n ? tail(datum)\n : error(datum, \"bad tagged datum -- contents\");\n}\n```", - "token_count": 296, + "content": "Here is the\nfunction:\n\n```javascript\njavascript_number_package\n base_operation_table\n\nfunction install_javascript_number_package() {\n function tag(x) {\n return attach_tag(\"javascript_number\", x);\n }\n put(\"add\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x + y));\n put(\"sub\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x - y));\n put(\"mul\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x * y));\n put(\"div\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x / y));\n put(\"make\", \"javascript_number\",\n x => tag(x));\n return \"done\";\n}\ninstall_javascript_number_package();\n\nfunction make_javascript_number(n) {\n return get(\"make\", \"javascript_number\")(n);\n}\n```", + "token_count": 70, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -910,8 +930,8 @@ "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_5" }, { - "content": "For\nsimplicity, we consider only the case where there are two\narguments.\nfunction:\n\n```javascript\njavascript_number_package\n base_operation_table\n\nfunction install_javascript_number_package() {\n function tag(x) {\n return attach_tag(\"javascript_number\", x);\n }\n put(\"add\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x + y));\n put(\"sub\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x - y));\n put(\"mul\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x * y));\n put(\"div\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x / y));\n put(\"make\", \"javascript_number\",\n x => tag(x));\n return \"done\";\n}\ninstall_javascript_number_package();\n\nfunction make_javascript_number(n) {\n return get(\"make\", \"javascript_number\")(n);\n}\n```", - "token_count": 79, + "content": "Here is the\nfunction:\n\n```javascript\ncoercion_support\n\n// coercion support\n\nlet coercion_list = null;\n\nfunction clear_coercion_list() {\n coercion_list = null;\n}\n\nfunction put_coercion(type1, type2, item) {\n if (is_undefined(get_coercion(type1, type2))) {\n coercion_list = pair(list(type1, type2, item),\n coercion_list);\n } else {\n return coercion_list;\n }\n}\n\nfunction get_coercion(type1, type2) {\n function get_type1(list_item) {\n return head(list_item);\n }\n function get_type2(list_item) {\n return head(tail(list_item));\n }\n function get_item(list_item) {\n return head(tail(tail(list_item)));\n }\n function get_coercion_iter(items) {\n if (is_null(items)) {\n return undefined;\n } else {\n const top = head(items);\n return equal(type1, get_type1(top)) &&\n equal(type2, get_type2(top))\n ? get_item(top)\n : get_coercion_iter(tail(items));\n }\n }\n return get_coercion_iter(coercion_list);\n}\n```\n\n```javascript\napply_generic_with_coercion_example\n base_operation_table\n javascript_number_package\n complex_number_package\n coercion_support\n\nfunction javascript_number_to_complex(n) {\n return make_complex_from_real_imag(contents(n), 0);\n}\n\nput_coercion(\"javascript_number\", \"complex\",\n javascript_number_to_complex);\n\nconst c = make_complex_from_real_imag(4, 3);\nconst n = make_javascript_number(7);\n\nadd(c, n);\n```\n\n```javascript\napply_generic_with_coercion\n apply_generic_with_coercion_example\n [ 'complex', [ 'rectangular', [ 11, 3 ] ] ]\n\nfunction apply_generic(op, args) {\n const type_tags = map(type_tag, args);\n const fun = get(op, type_tags);\n if (! is_undefined(fun)) {\n return apply(fun, map(contents, args));\n } else {\n if (length(args) === 2) {\n const type1 = head(type_tags);\n const type2 = head(tail(type_tags));\n const a1 = head(args);\n const a2 = head(tail(args));\n const t1_to_t2 = get_coercion(type1, type2);\n const t2_to_t1 = get_coercion(type2, type1);\n return ! is_undefined(t1_to_t2)\n ? apply_generic(op, list(t1_to_t2(a1), a2))\n : ! is_undefined(t2_to_t1)\n ? apply_generic(op, list(a1, t2_to_t1(a2)))\n : error(list(op, type_tags),\n \"no method for these types\");\n } else {\n return error(list(op, type_tags),\n \"no method for these types\");\n }\n }\n}\n```\n\nThis coercion has many advantages over the method of defining\nexplicit cross-type operations, as outlined above.\n\nAlthough we still\nneed to write coercion\nfunctions\nto relate the types (possibly $n^2$\nfunctions\nfor a system with $n$ types), we need to write\nonly one\nfunction\nfor each pair of types rather than a different\nfunction\nfor each collection of types and each generic operation.", + "token_count": 292, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -920,9 +940,9 @@ "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_6" }, { - "content": "For\nsimplicity, we consider only the case where there are two\narguments.\nfunction:\n\n```javascript\ncomplex_number_package\n base_operation_table\n\n// generic selector functions for complex numbers\n\nfunction real_part(z) {\n return apply_generic(\"real_part\", list(z));\n}\nfunction imag_part(z) {\n return apply_generic(\"imag_part\", list(z));\n}\nfunction magnitude(z) {\n return apply_generic(\"magnitude\", list(z));\n}\nfunction angle(z) {\n return apply_generic(\"angle\", list(z));\n}\nfunction square(x) {\n return x * x;\n}\n\nfunction install_rectangular_package() {\n function real_part(z) { return head(z); }\n function imag_part(z) { return tail(z); }\n function make_from_real_imag(x, y) { return pair(x, y); }\n function magnitude(z) {\n return math_sqrt(square(real_part(z)) +\n square(imag_part(z)));\n }\n function angle(z) {\n return math_atan2(imag_part(z), real_part(z));\n }\n function make_from_mag_ang(r, a) {\n return pair(r * math_cos(a), r * math_sin(a));\n }\n // interface to the rest of the system\n function tag(x) {\n return attach_tag(\"rectangular\", x);\n }\n put(\"real_part\", list(\"rectangular\"), real_part);\n put(\"imag_part\", list(\"rectangular\"), imag_part);\n put(\"magnitude\", list(\"rectangular\"), magnitude);\n put(\"angle\", list(\"rectangular\"), angle);\n put(\"make_from_real_imag\", \"rectangular\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"rectangular\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\ninstall_rectangular_package();\n\nfunction install_polar_package() {\n // internal functions\n function magnitude(z) { return head(z); }\n function angle(z) { return tail(z); }\n function make_from_mag_ang(r, a) { return pair(r, a); }\n function real_part(z) {\n return magnitude(z) * math_cos(angle(z));\n }\n function imag_part(z) {\n return magnitude(z) * math_sin(angle(z));\n }\n function make_from_real_imag(x, y) {\n return pair(math_sqrt(square(x) + square(y)),\n math_atan2(y, x));\n }\n\n // interface to the rest of the system\n function tag(x) { return attach_tag(\"polar\", x); }\n put(\"real_part\", list(\"polar\"), real_part);\n put(\"imag_part\", list(\"polar\"), imag_part);\n put(\"magnitude\", list(\"polar\"), magnitude);\n put(\"angle\", list(\"polar\"), angle);\n put(\"make_from_real_imag\", \"polar\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"polar\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\ninstall_polar_package();\n\nfunction install_complex_package() {\n // imported functions from rectangular and polar packages\n function make_from_real_imag(x, y) {\n return get(\"make_from_real_imag\", \"rectangular\")(x, y);\n }\n function make_from_mag_ang(r, a) {\n return get(\"make_from_mag_ang\", \"polar\")(r, a);\n }\n\n // internal functions\n function add_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) +\n real_part(z2),\n imag_part(z1) +\n imag_part(z2));\n }\n function sub_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) -\n real_part(z2),\n imag_part(z1) -\n imag_part(z2));\n }\n function mul_complex(z1, z2) {\n return make_from_mag_ang(magnitude(z1) *\n magnitude(z2),\n angle(z1) +\n angle(z2));\n }\n function div_complex(z1, z2) {\n return make_from_mag_ang(magnitude(z1) /\n magnitude(z2),\n angle(z1) -\n angle(z2));\n }\n\n // interface to rest of the system\n function tag(z) {\n return attach_tag(\"complex\", z);\n }\n put(\"add\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(add_complex(z1, z2)));\n put(\"sub\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(sub_complex(z1, z2)));\n put(\"mul\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(mul_complex(z1, z2)));\n put(\"div\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(div_complex(z1, z2)));\n put(\"make_from_real_imag\", \"complex\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"complex\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\ninstall_complex_package();\n\nfunction make_complex_from_real_imag(x, y){\n return get(\"make_from_real_imag\", \"complex\")(x, y);\n}\nfunction make_complex_from_mag_ang(r, a){\n return get(\"make_from_mag_ang\", \"complex\")(r, a);\n}\n```", - "token_count": 413, - "has_code": true, + "content": "Although we still\nneed to write coercion\nfunctions\nto relate the types (possibly $n^2$\nfunctions\nfor a system with $n$ types), we need to write\nonly one\nfunction\nfor each pair of types rather than a different\nfunction\nfor each collection of types and each generic operation.\n\nOn the other hand, there may be applications for which our coercion is not general enough.\n\nEven when neither of the objects to be\ncombined can be converted to the type of the other it may still be\npossible to perform the operation by converting both objects to a\nthird type.\n\nIn order to deal with such complexity and still preserve\nmodularity in our programs, it is usually necessary to build systems\nthat take advantage of still further structure in the relations among\ntypes, as we discuss next.\n\nThe coercion presented above relied on the existence of natural\nrelations between pairs of types.\n\nOften there is more global\nstructure in how the different types relate to each other.\n\nFor\ninstance, suppose we are building a generic arithmetic system to\nhandle integers, rational numbers, real numbers, and complex numbers.\n\nIn such a system, it is quite natural to regard an integer as a\nspecial kind of rational number, which is in turn a special kind of\nreal number, which is in turn a special kind of complex number.\n\nWhat\nwe actually have is a so-called hierarchy of types , in which,\nfor example, integers are a\nsubtype of rational numbers (i.e.,\nany operation that can be applied to a rational number can\nautomatically be applied to an integer).\n\nConversely, we say that\nrational numbers form a\nsupertype of integers.", + "token_count": 275, + "has_code": false, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", "subsection": "Combining Data of Different Types", @@ -930,9 +950,9 @@ "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_7" }, { - "content": "For\nsimplicity, we consider only the case where there are two\narguments.\nfunction:\n\n```javascript\ncoercion_support\n\n// coercion support\n\nlet coercion_list = null;\n\nfunction clear_coercion_list() {\n coercion_list = null;\n}\n\nfunction put_coercion(type1, type2, item) {\n if (is_undefined(get_coercion(type1, type2))) {\n coercion_list = pair(list(type1, type2, item),\n coercion_list);\n } else {\n return coercion_list;\n }\n}\n\nfunction get_coercion(type1, type2) {\n function get_type1(list_item) {\n return head(list_item);\n }\n function get_type2(list_item) {\n return head(tail(list_item));\n }\n function get_item(list_item) {\n return head(tail(tail(list_item)));\n }\n function get_coercion_iter(items) {\n if (is_null(items)) {\n return undefined;\n } else {\n const top = head(items);\n return equal(type1, get_type1(top)) &&\n equal(type2, get_type2(top))\n ? get_item(top)\n : get_coercion_iter(tail(items));\n }\n }\n return get_coercion_iter(coercion_list);\n}\n```\n\n```javascript\napply_generic_with_coercion_example\n base_operation_table\n javascript_number_package\n complex_number_package\n coercion_support\n\nfunction javascript_number_to_complex(n) {\n return make_complex_from_real_imag(contents(n), 0);\n}\n\nput_coercion(\"javascript_number\", \"complex\",\n javascript_number_to_complex);\n\nconst c = make_complex_from_real_imag(4, 3);\nconst n = make_javascript_number(7);\n\nadd(c, n);\n```\n\n```javascript\napply_generic_with_coercion\n apply_generic_with_coercion_example\n [ 'complex', [ 'rectangular', [ 11, 3 ] ] ]\n\nfunction apply_generic(op, args) {\n const type_tags = map(type_tag, args);\n const fun = get(op, type_tags);\n if (! is_undefined(fun)) {\n return apply(fun, map(contents, args));\n } else {\n if (length(args) === 2) {\n const type1 = head(type_tags);\n const type2 = head(tail(type_tags));\n const a1 = head(args);\n const a2 = head(tail(args));\n const t1_to_t2 = get_coercion(type1, type2);\n const t2_to_t1 = get_coercion(type2, type1);\n return ! is_undefined(t1_to_t2)\n ? apply_generic(op, list(t1_to_t2(a1), a2))\n : ! is_undefined(t2_to_t1)\n ? apply_generic(op, list(a1, t2_to_t1(a2)))\n : error(list(op, type_tags),\n \"no method for these types\");\n } else {\n return error(list(op, type_tags),\n \"no method for these types\");\n }\n }\n}\n```\n\nThis coercion has many advantages over the method of defining\nexplicit cross-type operations, as outlined above.\n\nAlthough we still\nneed to write coercion\nfunctions\nto relate the types (possibly $n^2$\nfunctions\nfor a system with $n$ types), we need to write\nonly one\nfunction\nfor each pair of types rather than a different\nfunction\nfor each collection of types and each generic operation.", - "token_count": 301, - "has_code": true, + "content": "Conversely, we say that\nrational numbers form a\nsupertype of integers.\n\nSuch a structure,\ncalled a tower , is illustrated in\nfigure.\n\nA tower of types.\n\nIf we have a tower structure, then we can greatly simplify the problem\nof adding a new type to the hierarchy, for we need only specify how\nthe new type is embedded in the next supertype above it and how it is\nthe supertype of the type below it.\n\nFor example, if we want to add an\ninteger to a complex number, we need not explicitly define a special\ncoercion\nfunction\ninteger_to_complex.\n\nInstead, we define how an integer can be transformed into a rational\nnumber, how a rational number is transformed into a real number, and how\na real number is transformed into a complex number.\n\nWe then allow the\nsystem to transform the integer into a complex number through these steps\nand then add the two complex numbers.\n\nWe can redesign our\napply_generic\nfunction\nin the following way: For each type, we need to supply a\nfunction,\nwhich raises objects of that type one level in the tower.\n\nThen when the system is required to operate on objects of different types\nit can successively raise the lower types until all the objects are at\nthe same level in the tower.\n\n(Exercises\nand\nconcern the details of implementing such a strategy.)\n\nAnother advantage of a tower is that we can easily implement the notion\nthat every type inherits all operations defined on a\nsupertype.\n\nFor instance, if we do not supply a special\nfunction\nfor finding the real part of an integer, we should nevertheless expect\nthat\nreal_part\nwill be defined for integers by virtue of the fact that integers are a\nsubtype of complex numbers.", + "token_count": 292, + "has_code": false, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", "subsection": "Combining Data of Different Types", @@ -940,8 +960,8 @@ "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_8" }, { - "content": "Although we still\nneed to write coercion\nfunctions\nto relate the types (possibly $n^2$\nfunctions\nfor a system with $n$ types), we need to write\nonly one\nfunction\nfor each pair of types rather than a different\nfunction\nfor each collection of types and each generic operation.\n\nOn the other hand, there may be applications for which our coercion is not general enough.\n\nEven when neither of the objects to be\ncombined can be converted to the type of the other it may still be\npossible to perform the operation by converting both objects to a\nthird type.\n\nIn order to deal with such complexity and still preserve\nmodularity in our programs, it is usually necessary to build systems\nthat take advantage of still further structure in the relations among\ntypes, as we discuss next.\n\nThe coercion presented above relied on the existence of natural\nrelations between pairs of types.\n\nOften there is more global\nstructure in how the different types relate to each other.\n\nFor\ninstance, suppose we are building a generic arithmetic system to\nhandle integers, rational numbers, real numbers, and complex numbers.\n\nIn such a system, it is quite natural to regard an integer as a\nspecial kind of rational number, which is in turn a special kind of\nreal number, which is in turn a special kind of complex number.\n\nWhat\nwe actually have is a so-called hierarchy of types , in which,\nfor example, integers are a\nsubtype of rational numbers (i.e.,\nany operation that can be applied to a rational number can\nautomatically be applied to an integer).\n\nConversely, we say that\nrational numbers form a\nsupertype of integers.", - "token_count": 275, + "content": "For instance, if we do not supply a special\nfunction\nfor finding the real part of an integer, we should nevertheless expect\nthat\nreal_part\nwill be defined for integers by virtue of the fact that integers are a\nsubtype of complex numbers.\n\nIf the required operation is not directly defined for the type of the\nobject given, we raise the object to its supertype and try again.\n\nWe thus\ncrawl up the tower, transforming our argument as we go, until we either\nfind a level at which the desired operation can be performed or hit the\ntop (in which case we give up).\n\nYet another advantage of a tower over a more general hierarchy is that\nit gives us a simple way to lower a data object to the\nsimplest representation.\n\nFor example, if we add\n$2+3i$ to $4-3i$ ,\nit would be nice to obtain the answer as the integer 6 rather than as the\ncomplex number $6+0i$.\n\nExercise discusses a way to implement\nsuch a lowering operation.\n\n(The trick is that we need a general way\nto distinguish those objects that can be lowered, such as\n$6+0i$ , from those that cannot, such as\n$6+2i$.)\n\nIf the data types in our system can be naturally arranged in a tower,\nthis greatly simplifies the problems of dealing with generic operations\non different types, as we have seen.\n\nUnfortunately, this is usually\nnot the case.\n\nFigure\nillustrates a more complex arrangement of mixed types, this one showing\nrelations among different types of geometric figures.\n\nWe see that, in\ngeneral,\na type may have more than one subtype.\n\nTriangles and quadrilaterals,\nfor instance, are both subtypes of polygons.\n\nIn addition, a type may\nhave more than one supertype.", + "token_count": 287, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -950,8 +970,8 @@ "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_9" }, { - "content": "Conversely, we say that\nrational numbers form a\nsupertype of integers.\n\nThe particular\nhierarchy we have here is of a very simple kind, in which each type\nhas at most one supertype and at most one subtype.\n\nSuch a structure,\ncalled a tower , is illustrated in\nfigure.\n\nA tower of types.\n\nIf we have a tower structure, then we can greatly simplify the problem\nof adding a new type to the hierarchy, for we need only specify how\nthe new type is embedded in the next supertype above it and how it is\nthe supertype of the type below it.\n\nFor example, if we want to add an\ninteger to a complex number, we need not explicitly define a special\ncoercion\nfunction\ninteger_to_complex.\n\nInstead, we define how an integer can be transformed into a rational\nnumber, how a rational number is transformed into a real number, and how\na real number is transformed into a complex number.\n\nWe then allow the\nsystem to transform the integer into a complex number through these steps\nand then add the two complex numbers.\n\napply_generic\nfunction\nin the following way: For each type, we need to supply a\nfunction,\nwhich raises objects of that type one level in the tower.\n\nThen when the system is required to operate on objects of different types\nit can successively raise the lower types until all the objects are at\nthe same level in the tower.\n\n(Exercises\nand\nconcern the details of implementing such a strategy.)\n\nAnother advantage of a tower is that we can easily implement the notion\nthat every type inherits all operations defined on a\nsupertype.", - "token_count": 272, + "content": "In addition, a type may\nhave more than one supertype.\n\nThis multiple-supertypes issue is particularly thorny,\nsince it means that there is no unique way to raise a type\nin the hierarchy.\n\nFinding the correct supertype in which\nto apply an operation to an object may involve considerable searching\nthrough the entire type network on the part of a\nfunction\nsuch as\napply_generic.\n\nSince there generally are multiple subtypes for a type, there is a similar\nproblem in coercing a value down the type hierarchy.\n\nDealing with large numbers of interrelated types while still preserving\nmodularity in the design of large systems is very difficult, and is an area\nof much current research.", + "token_count": 113, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -960,28 +980,8 @@ "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_10" }, { - "content": "Another advantage of a tower is that we can easily implement the notion\nthat every type inherits all operations defined on a\nsupertype.\n\nFor instance, if we do not supply a special\nfunction\nfor finding the real part of an integer, we should nevertheless expect\nthat\nreal_part\nwill be defined for integers by virtue of the fact that integers are a\nsubtype of complex numbers.\n\nIn a tower, we can arrange for this to happen\nin a uniform way by modifying\napply_generic.\n\nIf the required operation is not directly defined for the type of the\nobject given, we raise the object to its supertype and try again.\n\nWe thus\ncrawl up the tower, transforming our argument as we go, until we either\nfind a level at which the desired operation can be performed or hit the\ntop (in which case we give up).\n\nlower a data object to the\nsimplest representation.\n\nFor example, if we add\n$2+3i$ to $4-3i$ ,\nit would be nice to obtain the answer as the integer 6 rather than as the\ncomplex number $6+0i$.\n\nExercise discusses a way to implement\nsuch a lowering operation.\n\n(The trick is that we need a general way\nto distinguish those objects that can be lowered, such as\n$6+0i$ , from those that cannot, such as\n$6+2i$.)\n\nIf the data types in our system can be naturally arranged in a tower,\nthis greatly simplifies the problems of dealing with generic operations\non different types, as we have seen.\n\nUnfortunately, this is usually\nnot the case.\n\nFigure\nillustrates a more complex arrangement of mixed types, this one showing\nrelations among different types of geometric figures.\n\nWe see that, in\ngeneral,\nraise a type\nin the hierarchy.", - "token_count": 285, - "has_code": false, - "chapter": "Building Abstractions with Data", - "section": "Systems with Generic Operations", - "subsection": "Combining Data of Different Types", - "chunk_index": 11, - "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_11" - }, - { - "content": "We see that, in\ngeneral,\nraise a type\nin the hierarchy.\n\nFinding the correct supertype in which\nto apply an operation to an object may involve considerable searching\nthrough the entire type network on the part of a\nfunction\nsuch as\napply_generic.\n\nSince there generally are multiple subtypes for a type, there is a similar\nproblem in coercing a value down the type hierarchy.\n\nDealing with large numbers of interrelated types while still preserving\nmodularity in the design of large systems is very difficult, and is an area\nof much current research.", - "token_count": 92, - "has_code": false, - "chapter": "Building Abstractions with Data", - "section": "Systems with Generic Operations", - "subsection": "Combining Data of Different Types", - "chunk_index": 12, - "chunk_id": "Building_Abstractions_with_Data_Combining_Data_of_Different_Types_12" - }, - { - "content": "The manipulation of symbolic algebraic expressions is a complex\nprocess that illustrates many of the hardest problems that occur in\nthe design of large-scale systems.\n\nAn\ntypes, which are\noften useful for directing the processing of expressions.\n\nFor example, we\ncould describe the expression\n\\[ x^{2}\\, \\sin (y^2+1)+x\\, \\cos 2y+\\cos (y^3 -2y^2) \\]\nas a polynomial in $x$ with coefficients that\nare trigonometric functions of polynomials in\n$y$ whose coefficients are integers.\n\nWe will not attempt to develop a complete algebraic-manipulation\nsystem here.\n\nSuch systems are exceedingly complex programs, embodying\ndeep algebraic knowledge and elegant algorithms.\n\nWhat we will do is\nlook at a simple but important part of algebraic manipulation: the\narithmetic of polynomials.\n\nWe will illustrate the kinds of decisions\nthe designer of such a system faces, and how to apply the ideas of\nabstract data and generic operations to help organize this effort.\n\nOur first task in designing a system for performing arithmetic on\npolynomials is to decide just what a polynomial is.\n\nPolynomials are\nnormally defined relative to certain variables (the\nindeterminates of the polynomial).\n\nFor simplicity, we will\nrestrict ourselves to polynomials having just one indeterminate\n(univariate polynomials ).\n\\[ 5x^2 +3x +7 \\]\nis a simple polynomial in $x$ , and\n\\[ (y^2 +1)x^3 +(2y)x+1 \\]\nis a polynomial in $x$ whose coefficients are\npolynomials in $y$.\n\nAlready we are skirting some thorny issues.\n\nIs the first of these\npolynomials the same as the polynomial\n$5y^2 +3y +7$ , or not?\n\nA reasonable answer\nmight be yes, if we are considering a polynomial purely as a\nmathematical function, but no, if we are considering a polynomial to be a\nsyntactic form.\n\nThe second polynomial is algebraically equivalent\nto a polynomial in $y$ whose coefficients are\npolynomials in $x$.", - "token_count": 294, + "content": "The manipulation of symbolic algebraic expressions is a complex\nprocess that illustrates many of the hardest problems that occur in\nthe design of large-scale systems.\n\nAn\nalgebraic expression, in\ngeneral, can be viewed as a hierarchical structure, a tree of\noperators applied to operands.\n\nWe can construct algebraic expressions\nby starting with a set of primitive objects, such as constants and\nvariables, and combining these by means of algebraic operators, such\nas addition and multiplication.\n\nAs in other languages, we form\nabstractions that enable us to refer to compound objects in simple\nterms.\n\nTypical abstractions in symbolic algebra are ideas such as\nlinear combination, polynomial, rational function, or trigonometric\nfunction.\n\nWe can regard these as compound types, which are\noften useful for directing the processing of expressions.\n\nFor example, we\ncould describe the expression\n\\[ x^{2}\\, \\sin (y^2+1)+x\\, \\cos 2y+\\cos (y^3 -2y^2) \\]\nas a polynomial in $x$ with coefficients that\nare trigonometric functions of polynomials in\n$y$ whose coefficients are integers.\n\nWe will not attempt to develop a complete algebraic-manipulation\nsystem here.\n\nSuch systems are exceedingly complex programs, embodying\ndeep algebraic knowledge and elegant algorithms.\n\nWhat we will do is\nlook at a simple but important part of algebraic manipulation: the\narithmetic of polynomials.\n\nWe will illustrate the kinds of decisions\nthe designer of such a system faces, and how to apply the ideas of\nabstract data and generic operations to help organize this effort.\n\nOur first task in designing a system for performing arithmetic on\npolynomials is to decide just what a polynomial is.\n\nPolynomials are\nnormally defined relative to certain variables (the\nindeterminates of the polynomial).\n\nFor simplicity, we will\nrestrict ourselves to polynomials having just one indeterminate\n(univariate polynomials ).", + "token_count": 284, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -990,9 +990,9 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_1" }, { - "content": "The second polynomial is algebraically equivalent\nto a polynomial in $y$ whose coefficients are\npolynomials in $x$.\n\nShould our system recognize\nthis, or not?\n\nFurthermore, there are other ways to represent a\npolynomial for example, as a product of factors, or (for a\nunivariate polynomial) as the set of roots, or as a listing of the values\nof the polynomial at a specified set of points. polynomial will be a\nparticular syntactic form, not its underlying mathematical meaning.\n\nNow we must consider how to go about doing arithmetic on polynomials.\n\nIn this simple system, we will consider only addition and\nmultiplication.\n\nMoreover, we will insist that two polynomials to be\ncombined must have the same indeterminate.\n\nWe will approach the design of our system by following the familiar\ndiscipline of data abstraction.\n\nWe will represent polynomials using a\ndata structure called a\npoly , which consists of a variable and a\nterm_list\nthat extract those parts from a poly and a constructor\nmake_poly\nthat assembles a poly from a given variable and a term list.\n\nA variable will be just a\nstring,\nso we can use the\nis_same_variable\nfunction\nof section to compare\nvariables.\n\nThe following\nfunctions\ndefine\n\n```javascript\nadd_mul_poly\n install_javascript_number_package_usage\n make_polynomial_requires\n make_polynomial\n make_polynomial_example\n\nfunction add_poly(p1, p2) {\n return is_same_variable(variable(p1), variable(p2))\n ? make_poly(variable(p1),\n add_terms(term_list(p1), term_list(p2)))\n : error(list(p1, p2), \"polys not in same var -- add_poly\");\n}\nfunction mul_poly(p1, p2) {\n return is_same_variable(variable(p1), variable(p2))\n ? make_poly(variable(p1),\n mul_terms(term_list(p1), term_list(p2)))\n : error(list(p1, p2), \"polys not in same var -- mul_poly\");\n}\n```\n\nTo incorporate polynomials into our generic arithmetic system, we need\nto supply them with type tags.\n\nWe ll use the tag\n\"polynomial\",\nand install appropriate operations on tagged polynomials in the operation\ntable.", - "token_count": 282, - "has_code": true, + "content": "For simplicity, we will\nrestrict ourselves to polynomials having just one indeterminate\n(univariate polynomials ).\n\nA coefficient is defined as an algebraic expression\nthat is not dependent upon the indeterminate of the polynomial.\n\nFor\nexample,\n\\[ 5x^2 +3x +7 \\]\nis a simple polynomial in $x$ , and\n\\[ (y^2 +1)x^3 +(2y)x+1 \\]\nis a polynomial in $x$ whose coefficients are\npolynomials in $y$.\n\nAlready we are skirting some thorny issues.\n\nIs the first of these\npolynomials the same as the polynomial\n$5y^2 +3y +7$ , or not?\n\nA reasonable answer\nmight be yes, if we are considering a polynomial purely as a\nmathematical function, but no, if we are considering a polynomial to be a\nsyntactic form.\n\nThe second polynomial is algebraically equivalent\nto a polynomial in $y$ whose coefficients are\npolynomials in $x$.\n\nShould our system recognize\nthis, or not?\n\nFurthermore, there are other ways to represent a\npolynomial for example, as a product of factors, or (for a\nunivariate polynomial) as the set of roots, or as a listing of the values\nof the polynomial at a specified set of points.\n\nWe can finesse these questions by deciding that in our\nalgebraic-manipulation system a polynomial will be a\nparticular syntactic form, not its underlying mathematical meaning.\n\nNow we must consider how to go about doing arithmetic on polynomials.\n\nIn this simple system, we will consider only addition and\nmultiplication.\n\nMoreover, we will insist that two polynomials to be\ncombined must have the same indeterminate.\n\nWe will approach the design of our system by following the familiar\ndiscipline of data abstraction.\n\nWe will represent polynomials using a\ndata structure called a\npoly , which consists of a variable and a\ncollection of terms.", + "token_count": 285, + "has_code": false, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", "subsection": "Example: Symbolic Algebra", @@ -1000,8 +1000,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_2" }, { - "content": "We ll use the tag\n\"polynomial\",\nand install appropriate operations on tagged polynomials in the operation\ntable.\n\n```javascript\nWell embed all our code in an installation function\n\tfor the polynomial package,\n\tsimilar to the installation functions in\n\tsection:\n\n\t install_polynomial_package_template\n\t install_javascript_number_package_usage\n\t make_polynomial_requires\n\t make_polynomial\n\t make_polynomial_example\n\nfunction install_polynomial_package() {\n // internal functions\n // representation of poly\n function make_poly(variable, term_list) {\n return pair(variable, term_list);\n }\n function variable(p) { return head(p); }\n function term_list(p) { return tail(p); }\n functions is_same_variable and is_variable from section 2.3.2\n\n // representation of terms and term lists\n functions adjoin_term...coeff from text below\n\n function add_poly(p1, p2) { ... }\n functions used by add_poly\n function mul_poly(p1, p2) { ... }\n functions used by mul_poly\n\n // interface to rest of the system\n function tag(p) { return attach_tag(\"polynomial\", p); }\n put(\"add\", list(\"polynomial\", \"polynomial\"),\n (p1, p2) => tag(add_poly(p1, p2)));\n put(\"mul\", list(\"polynomial\", \"polynomial\"),\n (p1, p2) => tag(mul_poly(p1, p2)));\n put(\"make\", \"polynomial\",\n (variable, terms) => tag(make_poly(variable, terms)));\n return \"done\";\n}\n```\n\nPolynomial addition is performed termwise.\n\nTerms of the same order\n(i.e., with the same power of the indeterminate) must be combined.\n\nThis is done by forming a new term of the same order whose coefficient\nis the sum of the coefficients of the addends.\n\nTerms in one addend\nfor which there are no terms of the same order in the other addend are\nsimply accumulated into the sum polynomial being constructed.\n\nIn order to manipulate term lists, we will assume that we have a\nconstructor\nthe_empty_termlist\nthat returns an empty term list and a constructor\nadjoin_@term\nthat adjoins a new term to a term list.\n\nWe will also assume that we have\na predicate\nis_empty_termlist\nthat tells if a given term list is empty, a selector\nfirst_term\nthat extracts the highest-order term from a term list, and a selector\nrest_terms\nthat returns all but the highest-order term.", - "token_count": 300, + "content": "We will represent polynomials using a\ndata structure called a\npoly , which consists of a variable and a\ncollection of terms.\n\nA variable will be just a\nstring,\nso we can use the\nis_same_variable\nfunction\nof section to compare\nvariables.\n\nThe following\nfunctions\ndefine\naddition and multiplication of polys:\n\n```javascript\nadd_mul_poly\n install_javascript_number_package_usage\n make_polynomial_requires\n make_polynomial\n make_polynomial_example\n\nfunction add_poly(p1, p2) {\n return is_same_variable(variable(p1), variable(p2))\n ? make_poly(variable(p1),\n add_terms(term_list(p1), term_list(p2)))\n : error(list(p1, p2), \"polys not in same var -- add_poly\");\n}\nfunction mul_poly(p1, p2) {\n return is_same_variable(variable(p1), variable(p2))\n ? make_poly(variable(p1),\n mul_terms(term_list(p1), term_list(p2)))\n : error(list(p1, p2), \"polys not in same var -- mul_poly\");\n}\n```\n\nTo incorporate polynomials into our generic arithmetic system, we need\nto supply them with type tags.\n\nWe ll use the tag\n\"polynomial\",\nand install appropriate operations on tagged polynomials in the operation\ntable.\n\nWell embed all our code in an installation function for the polynomial package, similar to the installation functions in section: install_polynomial_package_template install_javascript_number_package_usage make_polynomial_requires make_polynomial make_polynomial_example function install_polynomial_package() { // internal functions // representation of poly function make_poly(variable, term_list) { return pair(variable, term_list); } function variable(p) { return head(p); } function term_list(p) { return tail(p); } functions is_same_variable and is_variable from section 2.3.2 // representation of terms and term lists functions adjoin_term...coeff from text below function add_poly(p1, p2) {... } functions used by add_poly function mul_poly(p1, p2) {... } functions used by mul_poly // interface to rest of the system function tag(p) { return attach_tag(\"polynomial\", p); } put(\"add\", list(\"polynomial\", \"polynomial\"), (p1, p2) => tag(add_poly(p1, p2))); put(\"mul\", list(\"polynomial\", \"polynomial\"), (p1, p2) => tag(mul_poly(p1, p2))); put(\"make\", \"polynomial\", (variable, terms) => tag(make_poly(variable, terms))); return \"done\"; }\n\nPolynomial addition is performed termwise.\n\nTerms of the same order\n(i.e., with the same power of the indeterminate) must be combined.", + "token_count": 287, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -1010,8 +1010,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_3" }, { - "content": "We will also assume that we have\na predicate\nis_empty_termlist\nthat tells if a given term list is empty, a selector\nfirst_term\nthat extracts the highest-order term from a term list, and a selector\nrest_terms\nthat returns all but the highest-order term.\n\nTo manipulate terms,\nwe will suppose that we have a constructor\nmake_term\nthat constructs a term with given order and coefficient, and selectors\n\nHere is the function that constructs the term list for the sum of two\n\n```javascript\npolynomials;\n```\n\n```javascript\nnote that we slightly extend the syntax of\n\t by admitting another conditional\n statement in place of the block following\n else:\n```\n\n```javascript\nadd_terms\n install_javascript_number_package_usage\n make_polynomial_requires\n make_polynomial\n make_polynomial_example\n\nfunction add_terms(L1, L2) {\n if (is_empty_termlist(L1)) {\n return L2;\n } else if (is_empty_termlist(L2)) {\n return L1;\n } else {\n const t1 = first_term(L1);\n const t2 = first_term(L2);\n return order(t1) > order(t2)\n ? adjoin_term(t1, add_terms(rest_terms(L1), L2))\n : order(t1) < order(t2)\n ? adjoin_term(t2, add_terms(L1, rest_terms(L2)))\n : adjoin_term(make_term(order(t1),\n add(coeff(t1), coeff(t2))),\n add_terms(rest_terms(L1),\n rest_terms(L2)));\n }\n}\n```\n\nThe most important point to note here is that we used the generic addition function\n\nIn order to multiply two term lists, we multiply each term of the first\nlist by all the terms of the other list, repeatedly using\nmul_term_by_all_terms,\nwhich multiplies a given term by all terms in a given term list.\n\nThe\nresulting term lists (one for each term of the first list) are accumulated\ninto a sum.\n\nMultiplying two terms forms a term whose order is the sum of\nthe orders of the factors and whose coefficient is the product of the\ncoefficients of the factors:", - "token_count": 262, + "content": "Terms of the same order\n(i.e., with the same power of the indeterminate) must be combined.\n\nTerms in one addend\nfor which there are no terms of the same order in the other addend are\nsimply accumulated into the sum polynomial being constructed.\n\nIn order to manipulate term lists, we will assume that we have a\nconstructor\nthe_empty_termlist\nthat returns an empty term list and a constructor\nadjoin_@term\nthat adjoins a new term to a term list.\n\nWe will also assume that we have\na predicate\nis_empty_termlist\nthat tells if a given term list is empty, a selector\nfirst_term\nthat extracts the highest-order term from a term list, and a selector\nrest_terms\nthat returns all but the highest-order term.\n\nTo manipulate terms,\nwe will suppose that we have a constructor\nmake_term\nthat constructs a term with given order and coefficient, and selectors\nand\nthat return, respectively, the order\nand the coefficient of the term.\n\nThese operations allow us to consider\nboth terms and term lists as data abstractions, whose concrete\nrepresentations we can worry about separately.\n\nHere is the function that constructs the term list for the sum of two polynomials; note that we slightly extend the syntax of conditional statements\n\ndescribed in section by admitting another conditional statement in place of the block following else:\n\n```javascript\nadd_terms\n install_javascript_number_package_usage\n make_polynomial_requires\n make_polynomial\n make_polynomial_example\n\nfunction add_terms(L1, L2) {\n if (is_empty_termlist(L1)) {\n return L2;\n } else if (is_empty_termlist(L2)) {\n return L1;\n } else {\n const t1 = first_term(L1);\n const t2 = first_term(L2);\n return order(t1) > order(t2)\n ? adjoin_term(t1, add_terms(rest_terms(L1), L2))\n : order(t1) < order(t2)\n ? adjoin_term(t2, add_terms(L1, rest_terms(L2)))\n : adjoin_term(make_term(order(t1),\n add(coeff(t1), coeff(t2))),\n add_terms(rest_terms(L1),\n rest_terms(L2)));\n }\n}\n```\n\nThe most important point to note here is that we used the generic addition\nfunction\nto add together the coefficients of the\nterms being combined.", + "token_count": 299, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -1020,8 +1020,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_4" }, { - "content": "Multiplying two terms forms a term whose order is the sum of\nthe orders of the factors and whose coefficient is the product of the\ncoefficients of the factors:\n\n```javascript\nmul_terms\n install_javascript_number_package_usage\n make_polynomial_requires\n make_polynomial\n make_polynomial_example\n\nfunction mul_terms(L1, L2) {\n return is_empty_termlist(L1)\n ? the_empty_termlist\n : add_terms(mul_term_by_all_terms(\n first_term(L1), L2),\n mul_terms(rest_terms(L1), L2));\n}\nfunction mul_term_by_all_terms(t1, L) {\n if (is_empty_termlist(L)) {\n return the_empty_termlist;\n } else {\n const t2 = first_term(L);\n return adjoin_term(\n make_term(order(t1) + order(t2),\n mul(coeff(t1), coeff(t2))),\n mul_term_by_all_terms(t1, rest_terms(L)));\n }\n}\n```\n\nThis is really all there is to polynomial addition and multiplication.\n\nNotice that, since we operate on terms using the generic\nfunctions\n,\nthen we also are automatically able to handle operations on\npolynomials of different coefficient types, such as\n\\[\n\\begin{array}{l}\n{\\left[3x^2 +(2+3i)x+7\\right] \\cdot \\left[x^4 +\\frac{2}{3}x^2\n+(5+3i)\\right]}\n\\end{array}\n\\]\n\nBecause we installed the polynomial addition and multiplication\nfunctions\nadd_@poly\nand\nmul_poly\nin the generic arithmetic system as the\n\\[\n\\begin{array}{l}\n{\\left[ (y+1)x^2 +(y^2 +1)x+(y-1)\\right]\\cdot \\left[(y-2)x+(y^3 +7)\\right]}\n\\end{array}\n\\]\nThe reason is that when the system tries to combine coefficients, it\nwill dispatch through $y$ ), these will be combined\nusing\nadd_poly\nand\nmul_poly.\n\nThe result is a kind of\ndata-directed recursion in which, for example, a call to\nmul_poly\nwill result in recursive calls to\nmul_poly\nin order to multiply the coefficients.\n\nIf the coefficients of the\ncoefficients were themselves polynomials (as might be used to represent\npolynomials in three variables), the data direction would ensure that the\nsystem would follow through another level of recursive calls, and so on\nthrough as many levels as the structure of the data dictates.\n\nFinally, we must confront the job of implementing a good\nrepresentation for term lists.\n\nA term list is, in effect, a set of\ncoefficients keyed by the order of the term.", - "token_count": 291, + "content": "The most important point to note here is that we used the generic addition\nfunction\nto add together the coefficients of the\nterms being combined.\n\nIn order to multiply two term lists, we multiply each term of the first\nlist by all the terms of the other list, repeatedly using\nmul_term_by_all_terms,\nwhich multiplies a given term by all terms in a given term list.\n\nThe\nresulting term lists (one for each term of the first list) are accumulated\ninto a sum.\n\nMultiplying two terms forms a term whose order is the sum of\nthe orders of the factors and whose coefficient is the product of the\ncoefficients of the factors:\n\n```javascript\nmul_terms\n install_javascript_number_package_usage\n make_polynomial_requires\n make_polynomial\n make_polynomial_example\n\nfunction mul_terms(L1, L2) {\n return is_empty_termlist(L1)\n ? the_empty_termlist\n : add_terms(mul_term_by_all_terms(\n first_term(L1), L2),\n mul_terms(rest_terms(L1), L2));\n}\nfunction mul_term_by_all_terms(t1, L) {\n if (is_empty_termlist(L)) {\n return the_empty_termlist;\n } else {\n const t2 = first_term(L);\n return adjoin_term(\n make_term(order(t1) + order(t2),\n mul(coeff(t1), coeff(t2))),\n mul_term_by_all_terms(t1, rest_terms(L)));\n }\n}\n```\n\nThis is really all there is to polynomial addition and multiplication.\n\nNotice that, since we operate on terms using the generic\nfunctions\nand ,\nour polynomial package is automatically able to handle any type of\ncoefficient that is known about by the generic arithmetic package.\n\nIf we include a\ncoercion mechanism such as one of those discussed in\nsection ,\nthen we also are automatically able to handle operations on\npolynomials of different coefficient types, such as\n\\[\n\\begin{array}{l}\n{\\left[3x^2 +(2+3i)x+7\\right] \\cdot \\left[x^4 +\\frac{2}{3}x^2\n+(5+3i)\\right]}\n\\end{array}\n\\]", + "token_count": 246, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -1030,9 +1030,9 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_5" }, { - "content": "A term list is, in effect, a set of\ncoefficients keyed by the order of the term.\n\nHence, any of the\nmethods for representing sets, as discussed in\nsection , can be applied to this\ntask.\n\nOn the other hand, our\nfunctions\nadd_terms\nand\nmul_terms\nalways access term lists sequentially from highest to lowest order.\n\nThus, we will use some kind of ordered list representation.\n\nHow should we structure the list that represents a term list?\n\nOne\nconsideration is the density of the polynomials we intend\nto manipulate.\n\nA polynomial is said to be\ndense if it has nonzero coefficients in terms of most orders.\n\nIf it has many zero terms it is said to be\nsparse.\n\nFor example,\n\\[ A:\\quad x^5 +2x^4 +3x^2 -2x -5 \\]\nis a dense polynomial, whereas\n\\[ B:\\quad x^{100} +2x^2 +1 \\]\nis sparse.\n\n```javascript\nThe term list of a dense polynomial is most efficiently represented\n\tas a list of the coefficients.\n```\n\nFor example,\nthe polynomial\n$A$ above would be nicely represented as\nlist(1, 2, 0, 3, -2, -5).\n\nThe order of a term in this representation is the length of the sublist\nbeginning with that term s coefficient, decremented by 1. $B$ : There would be a giant list of zeros\npunctuated by a few lonely nonzero terms.\n\nA more reasonable representation\nof the term list of a sparse polynomial is as a list of the nonzero terms,\nwhere each term is a list containing the order of the term and the\ncoefficient for that order.\n\nIn such a , polynomial\n$B$ is efficiently represented as\nlist(list(100, 1), list(2, 2), list(0, 1)).\n\nAs most polynomial manipulations are performed on sparse polynomials, we\nwill use this method.\n\nWe will assume that term lists are represented as\nlists of terms, arranged from highest-order to lowest-order term.", - "token_count": 302, - "has_code": true, + "content": "If we include a\ncoercion mechanism such as one of those discussed in\nsection ,\nthen we also are automatically able to handle operations on\npolynomials of different coefficient types, such as\n\\[\n\\begin{array}{l}\n{\\left[3x^2 +(2+3i)x+7\\right] \\cdot \\left[x^4 +\\frac{2}{3}x^2\n+(5+3i)\\right]}\n\\end{array}\n\\]\n\nSince the coefficients are themselves\npolynomials (in $y$ ), these will be combined\nusing\nadd_poly\nand\nmul_poly.\n\nThe result is a kind of\ndata-directed recursion in which, for example, a call to\nmul_poly\nwill result in recursive calls to\nmul_poly\nin order to multiply the coefficients.\n\nIf the coefficients of the\ncoefficients were themselves polynomials (as might be used to represent\npolynomials in three variables), the data direction would ensure that the\nsystem would follow through another level of recursive calls, and so on\nthrough as many levels as the structure of the data dictates.\n\nFinally, we must confront the job of implementing a good\nrepresentation for term lists.\n\nA term list is, in effect, a set of\ncoefficients keyed by the order of the term.\n\nHence, any of the\nmethods for representing sets, as discussed in\nsection , can be applied to this\ntask.\n\nOn the other hand, our\nfunctions\nadd_terms\nand\nmul_terms\nalways access term lists sequentially from highest to lowest order.\n\nThus, we will use some kind of ordered list representation.\n\nHow should we structure the list that represents a term list?\n\nOne\nconsideration is the density of the polynomials we intend\nto manipulate.\n\nA polynomial is said to be\ndense if it has nonzero coefficients in terms of most orders.\n\nIf it has many zero terms it is said to be\nsparse.\n\nFor example,\n\\[ A:\\quad x^5 +2x^4 +3x^2 -2x -5 \\]\nis a dense polynomial, whereas\n\\[ B:\\quad x^{100} +2x^2 +1 \\]\nis sparse.", + "token_count": 291, + "has_code": false, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", "subsection": "Example: Symbolic Algebra", @@ -1040,8 +1040,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_6" }, { - "content": "We will assume that term lists are represented as\nlists of terms, arranged from highest-order to lowest-order term.\n\nOnce we\nhave made this decision, implementing the selectors and constructors for\nterms and term lists is straightforward:\n\n```javascript\nadjoin_term\n install_javascript_number_package_usage\n make_polynomial_requires\n make_polynomial\n make_polynomial_example\n\nfunction adjoin_term(term, term_list) {\n return is_equal_to_zero(coeff(term))\n ? term_list\n : pair(term, term_list);\n}\n\nconst the_empty_termlist = null;\n\nfunction first_term(term_list) { return head(term_list); }\n\nfunction rest_terms(term_list) { return tail(term_list); }\n\nfunction is_empty_termlist(term_list) { return is_null(term_list); }\n\nfunction make_term(order, coeff) { return list(order, coeff); }\n\nfunction order(term) { return head(term); }\n\nfunction coeff(term) { return head(tail(term)); }\n```\n\nwhere\nis_equal_to_zero\nis as defined in exercise.\n\n(See also\nexercise below.)\n\nUsers of the polynomial package will create (tagged) polynomials by means of the function:", - "token_count": 122, + "content": "For example,\n\\[ A:\\quad x^5 +2x^4 +3x^2 -2x -5 \\]\nis a dense polynomial, whereas\n\\[ B:\\quad x^{100} +2x^2 +1 \\]\nis sparse.\n\nFor example,\nthe polynomial\n$A$ above would be nicely represented as\nlist(1, 2, 0, 3, -2, -5).\n\nThe order of a term in this representation is the length of the sublist\nbeginning with that term s coefficient, decremented by 1.\n\nThis would be a terrible representation for a sparse polynomial such as\n$B$ : There would be a giant list of zeros\npunctuated by a few lonely nonzero terms.\n\nA more reasonable representation\nof the term list of a sparse polynomial is as a list of the nonzero terms,\nwhere each term is a list containing the order of the term and the\ncoefficient for that order.\n\nIn such a , polynomial\n$B$ is efficiently represented as\nlist(list(100, 1), list(2, 2), list(0, 1)).\n\nAs most polynomial manipulations are performed on sparse polynomials, we\nwill use this method.\n\nWe will assume that term lists are represented as\nlists of terms, arranged from highest-order to lowest-order term.\n\nOnce we\nhave made this decision, implementing the selectors and constructors for\nterms and term lists is straightforward:\n\n```javascript\nadjoin_term\n install_javascript_number_package_usage\n make_polynomial_requires\n make_polynomial\n make_polynomial_example\n\nfunction adjoin_term(term, term_list) {\n return is_equal_to_zero(coeff(term))\n ? term_list\n : pair(term, term_list);\n}\n\nconst the_empty_termlist = null;\n\nfunction first_term(term_list) { return head(term_list); }\n\nfunction rest_terms(term_list) { return tail(term_list); }\n\nfunction is_empty_termlist(term_list) { return is_null(term_list); }\n\nfunction make_term(order, coeff) { return list(order, coeff); }\n\nfunction order(term) { return head(term); }\n\nfunction coeff(term) { return head(tail(term)); }\n```\n\nwhere\nis_equal_to_zero\nis as defined in exercise.\n\n(See also\nexercise below.)\n\nUsers of the polynomial package will create (tagged) polynomials by means of the function:", + "token_count": 282, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -1050,8 +1050,8 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_7" }, { - "content": "Users of the polynomial package will create (tagged) polynomials by means of the function:\n\n```javascript\nmake_polynomial_requires\n is_same_variable\n\nfunction install_javascript_number_is_equal_to_zero() {\n put(\"is_equal_to_zero\", list(\"javascript_number\"),\n x => x === 0);\n return \"done\";\n}\ninstall_javascript_number_is_equal_to_zero();\n\nfunction is_equal_to_zero(x) {\n return apply_generic(\"is_equal_to_zero\", list(x));\n}\n\nfunction install_polynomial_package() {\n\n // internal functions\n\n // representation of poly\n function make_poly(variable, term_list) {\n return pair(variable, term_list);\n }\n function variable(p) { return head(p); }\n function term_list(p) { return tail(p); }\n\n // representation of terms and term lists\n function adjoin_term(term, term_list) {\n return is_equal_to_zero(coeff(term))\n ? term_list\n : pair(term, term_list);\n }\n const the_empty_termlist = null;\n function first_term(term_list) {\n return head(term_list);\n }\n function rest_terms(term_list) {\n return tail(term_list);\n }\n function is_empty_termlist(term_list) {\n return is_null(term_list);\n }\n function make_term(order, coeff) {\n return list(order, coeff);\n }\n function order(term) {\n return head(term);\n }\n function coeff(term) {\n return head(tail(term));\n }\n\n function add_poly(p1, p2) {\n return is_same_variable(variable(p1), variable(p2))\n ? make_poly(variable(p1),\n add_terms(term_list(p1),\n term_list(p2)))\n : error(list(p1, p2),\n \"polys not in same var -- add_poly\");\n }\n\n function add_terms(L1, L2) {\n if (is_empty_termlist(L1)) {\n return L2;\n }\n else if (is_empty_termlist(L2)) {\n return L1;\n }\n else {\n const t1 = first_term(L1);\n const t2 = first_term(L2);\n return order(t1) > order(t2)\n ? adjoin_term(t1, add_terms(rest_terms(L1), L2))\n : order(t1) < order(t2)\n ? adjoin_term(t2, add_terms(L1, rest_terms(L2)))\n : adjoin_term(make_term(order(t1),\n add(coeff(t1),\n coeff(t2))),\n add_terms(rest_terms(L1),\n rest_terms(L2)));\n }\n }\n\n function mul_poly(p1, p2) {\n return is_same_variable(variable(p1), variable(p2))\n ? make_poly(variable(p1),\n mul_terms(term_list(p1),\n term_list(p2)))\n : error(list(p1, p2),\n \"polys not in same var -- mul_poly\");\n }\n\n function mul_terms(L1, L2) {\n return is_empty_termlist(L1)\n ? the_empty_termlist\n : add_terms(mul_term_by_all_terms(\n first_term(L1), L2),\n mul_terms(rest_terms(L1), L2));\n }\n function mul_term_by_all_terms(t1, L) {\n if (is_empty_termlist(L)) {\n return the_empty_termlist;\n } else {\n const t2 = first_term(L);\n return adjoin_term(\n make_term(order(t1) + order(t2),\n mul(coeff(t1), coeff(t2))),\n mul_term_by_all_terms(t1, rest_terms(L)));\n }\n }\n\n // interface to rest of the system\n function tag(p) {\n return attach_tag(\"polynomial\", p);\n }\n put(\"add\", list(\"polynomial\", \"polynomial\"),\n (p1, p2) => tag(add_poly(p1, p2)));\n put(\"mul\", list(\"polynomial\", \"polynomial\"),\n (p1, p2) => tag(mul_poly(p1, p2)));\n put(\"make\", \"polynomial\",\n (variable, terms) =>\n tag(make_poly(variable, terms)));\n return \"done\";\n}\ninstall_polynomial_package();\n```", - "token_count": 308, + "content": "Users of the polynomial package will create (tagged) polynomials by means of the function:\n\n```javascript\nmake_polynomial\n install_javascript_number_package_usage\n make_polynomial_requires\n make_polynomial_example\n [ 3, [ [ 'javascript_number', 23 ], null ] ]\n\nfunction make_polynomial(variable, terms) {\n return get(\"make\", \"polynomial\")(variable, terms);\n}\n```\n\n```javascript\nmake_polynomial_example\n\nconst p1 = make_polynomial(\"x\",\n list(make_term(2, make_javascript_number(4)),\n make_term(1, make_javascript_number(3)),\n make_term(0, make_javascript_number(7))));\nconst p2 = make_polynomial(\"x\",\n list(make_term(2, make_javascript_number(5)),\n make_term(1, make_javascript_number(2)),\n make_term(0, make_javascript_number(10))));\n\nmul(p1, p2);\n\nconst p1 = make_polynomial(\"x\",\n list(list(2, make_javascript_number(4)),\n list(1, make_javascript_number(3)),\n list(0, make_javascript_number(7))));\nconst p2 = make_polynomial(\"x\",\n list(list(2, make_javascript_number(5)),\n list(1, make_javascript_number(2)),\n list(0, make_javascript_number(10))));\n\nhead(tail(tail(tail(mul(p1, p2)))));\n```\n\nOur polynomial system illustrates how objects of one type\n(polynomials) may in fact be complex objects that have objects of many\ndifferent types as parts.\n\nThis poses no real difficulty in defining\ngeneric operations.\n\nWe need only install appropriate generic operations\nfor performing the necessary manipulations of the parts of the\ncompound types.\n\nIn fact, we saw that polynomials form a kind of\nrecursive data abstraction, in that parts of a polynomial may\nthemselves be polynomials.\n\nOur generic operations and our\ndata-directed programming style can handle this complication without\nmuch trouble.\n\nOn the other hand, polynomial algebra is a system for which the data\ntypes cannot be naturally arranged in a tower.\n\nFor instance, it is\npossible to have polynomials in $x$ whose\ncoefficients are polynomials in $y$.\n\nIt is also\npossible to have polynomials in $y$ whose\ncoefficients are polynomials in $x$.\n\nNeither of\nthese types is above the other in any natural way, yet it is\noften necessary to add together elements from each set.\n\nThere are several\nways to do this.\n\nOne possibility is to convert one polynomial to the type\nof the other by expanding and rearranging terms so that both polynomials\nhave the same principal variable.", + "token_count": 287, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -1060,9 +1060,9 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_8" }, { - "content": "Users of the polynomial package will create (tagged) polynomials by means of the function:\n\n```javascript\nmake_polynomial\n install_javascript_number_package_usage\n make_polynomial_requires\n make_polynomial_example\n [ 3, [ [ 'javascript_number', 23 ], null ] ]\n\nfunction make_polynomial(variable, terms) {\n return get(\"make\", \"polynomial\")(variable, terms);\n}\n```\n\n```javascript\nmake_polynomial_example\n\nconst p1 = make_polynomial(\"x\",\n list(make_term(2, make_javascript_number(4)),\n make_term(1, make_javascript_number(3)),\n make_term(0, make_javascript_number(7))));\nconst p2 = make_polynomial(\"x\",\n list(make_term(2, make_javascript_number(5)),\n make_term(1, make_javascript_number(2)),\n make_term(0, make_javascript_number(10))));\n\nmul(p1, p2);\n\nconst p1 = make_polynomial(\"x\",\n list(list(2, make_javascript_number(4)),\n list(1, make_javascript_number(3)),\n list(0, make_javascript_number(7))));\nconst p2 = make_polynomial(\"x\",\n list(list(2, make_javascript_number(5)),\n list(1, make_javascript_number(2)),\n list(0, make_javascript_number(10))));\n\nhead(tail(tail(tail(mul(p1, p2)))));\n```\n\nOur polynomial system illustrates how objects of one type\n(polynomials) may in fact be complex objects that have objects of many\ndifferent types as parts.\n\nThis poses no real difficulty in defining\ngeneric operations.\n\nWe need only install appropriate generic operations\nfor performing the necessary manipulations of the parts of the\ncompound types.\n\nIn fact, we saw that polynomials form a kind of\nrecursive data abstraction, in that parts of a polynomial may\nthemselves be polynomials.\n\nOur generic operations and our\ndata-directed programming style can handle this complication without\nmuch trouble.\n\nOn the other hand, polynomial algebra is a system for which the data\ntypes cannot be naturally arranged in a tower.\n\nFor instance, it is\npossible to have polynomials in $x$ whose\ncoefficients are polynomials in $y$.\n\nIt is also\npossible to have polynomials in $y$ whose\ncoefficients are polynomials in $x$.\n\nNeither of\nthese types is above the other in any natural way, yet it is\noften necessary to add together elements from each set.\n\nThere are several\nways to do this.\n\nOne possibility is to convert one polynomial to the type\nof the other by expanding and rearranging terms so that both polynomials\nhave the same principal variable.", - "token_count": 287, - "has_code": true, + "content": "One possibility is to convert one polynomial to the type\nof the other by expanding and rearranging terms so that both polynomials\nhave the same principal variable.\n\nThis strategy works fairly well, except that the conversion may expand\na polynomial unnecessarily, making it hard to read and perhaps less\nefficient to work with.\n\nThe tower strategy is certainly not natural\nfor this domain or for any domain where the user can invent new types\ndynamically using old types in various combining forms, such as\ntrigonometric functions, power series, and integrals.\n\nIt should not be surprising that controlling\ncoercion is a serious problem in the design of large-scale\nalgebraic-manipulation systems.\n\nMuch of the complexity of such systems is\nconcerned with relationships among diverse types.\n\nIndeed, it is fair to\nsay that we do not yet completely understand coercion.\n\nIn fact, we do not\nyet completely understand the concept of a data type.\n\nNevertheless, what\nwe know provides us with powerful structuring and modularity principles to\nsupport the design of large systems.\n\nWe can extend our generic arithmetic system to include rational\nfunctions.\n\nThese are fractions whose numerator and\ndenominator are polynomials, such as\n\\[\n\\begin{array}{l}\n\\dfrac{x+1}{x^3 -1}\n\\end{array}\n\\]\nThe system should be able to add, subtract, multiply, and divide\nrational functions, and to perform such computations as\n\\[\n\\begin{array}{lll}\n\\dfrac{x+1}{x^3 -1}+\\dfrac{x}{x^2 -1} & = & \\dfrac{x^3 +2x^2 +3x +1}{x^4 +\nx^3 -x-1}\n\\end{array}\n\\]\n(Here the sum has been simplified by removing common factors.\n\nOrdinary cross multiplication would have produced a\nfourth-degree polynomial over a fifth-degree polynomial.)\n\nIf we modify our rational-arithmetic package so that it uses generic operations, then it will do what we want, except for the problem of reducing\n\nfractions to lowest terms.", + "token_count": 286, + "has_code": false, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", "subsection": "Example: Symbolic Algebra", @@ -1070,9 +1070,9 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_9" }, { - "content": "One possibility is to convert one polynomial to the type\nof the other by expanding and rearranging terms so that both polynomials\nhave the same principal variable.\n\nOne can impose a towerlike structure on\nthis by ordering the variables and thus always converting any polynomial\nto a\ncanonical form with the highest-priority variable\ndominant and the lower-priority variables buried in the coefficients.\n\nThis strategy works fairly well, except that the conversion may expand\na polynomial unnecessarily, making it hard to read and perhaps less\nefficient to work with.\n\nThe tower strategy is certainly not natural\nfor this domain or for any domain where the user can invent new types\ndynamically using old types in various combining forms, such as\ntrigonometric functions, power series, and integrals.\n\nIt should not be surprising that controlling\n\nWe can extend our generic arithmetic system to include rational\nfunctions.\n\nThese are fractions whose numerator and\ndenominator are polynomials, such as\n\\[\n\\begin{array}{l}\n\\dfrac{x+1}{x^3 -1}\n\\end{array}\n\\]\nThe system should be able to add, subtract, multiply, and divide\nrational functions, and to perform such computations as\n\\[\n\\begin{array}{lll}\n\\dfrac{x+1}{x^3 -1}+\\dfrac{x}{x^2 -1} & = & \\dfrac{x^3 +2x^2 +3x +1}{x^4 +\nx^3 -x-1}\n\\end{array}\n\\]\n(Here the sum has been simplified by removing common factors.\n\nOrdinary cross multiplication would have produced a\nfourth-degree polynomial over a fifth-degree polynomial.)\n\nIf we modify our rational-arithmetic package so that it uses generic operations, then it will do what we want, except for the problem of reducing\n\nfractions to lowest terms.\n\nWe can reduce polynomial fractions to lowest terms using the same idea\nwe used with integers: modifying\nmake_rat\nto divide both the numerator and the denominator by their greatest common\ndivisor.\n\nThe notion of\ngreatest common divisor makes sense for polynomials.", - "token_count": 289, - "has_code": false, + "content": "fractions to lowest terms.\n\nThe notion of\ngreatest common divisor makes sense for polynomials.\n\nIn\nfact, we can compute the GCD of two polynomials using essentially the\nsame Euclid s Algorithm that works for integers.\n\nThe\ninteger version is\n\n```javascript\nfunction gcd(a, b) {\n return b === 0\n ? a\n : gcd(b, a % b);\n}\n```\n\nUsing this, we could make the obvious modification to define a GCD operation that works on term lists:\n\n```javascript\nfunction gcd_terms(a, b) {\n return is_empty_termlist(b)\n ? a\n : gcd_terms(b, remainder_terms(a, b));\n}\n```\n\nwhere remainder_terms picks out the remainder component of the list returned by the term-list division operation div_terms that was implemented in exercise.\n\nWe can solve the problem exhibited in\nexercise if\nwe use the following modification of the GCD algorithm (which really\nworks only in the case of polynomials with integer coefficients).\n\nBefore performing any polynomial division in the GCD computation, we\nmultiply the dividend by an integer constant factor, chosen to\nguarantee that no fractions will arise during the division process.\n\nOur answer will thus differ from the actual GCD by an integer constant\nfactor, but this does not matter in the case of reducing rational\nfunctions to lowest terms; the GCD will be used to divide both the\nnumerator and denominator, so the integer constant factor will cancel\nout.\n\nMore precisely, if $P$ and\n$Q$ are polynomials, let\n$O_1$ be the order of\n$P$ (i.e., the order of the largest term of\n$P$ ) and let $O_2$\nbe the order of $Q$.\n\nLet\n$c$ be the leading coefficient of\n$Q$.\n\nThen it can be shown that, if we multiply\n$P$ by the\nintegerizing factor\n$c^{1+O_{1} -O_{2}}$ , the resulting polynomial\ncan be divided by $Q$ by using the\ndiv_terms\nalgorithm without introducing any fractions.", + "token_count": 296, + "has_code": true, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", "subsection": "Example: Symbolic Algebra", @@ -1080,28 +1080,18 @@ "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_10" }, { - "content": "The notion of\ngreatest common divisor makes sense for polynomials.\n\nIn\nfact, we can compute the GCD of two polynomials using essentially the\nsame Euclid s Algorithm that works for integers.\n\n```javascript\nfunction gcd(a, b) {\n return b === 0\n ? a\n : gcd(b, a % b);\n}\n```\n\nUsing this, we could make the obvious modification to define a GCD operation that works on term lists:\n\n```javascript\nfunction gcd_terms(a, b) {\n return is_empty_termlist(b)\n ? a\n : gcd_terms(b, remainder_terms(a, b));\n}\n```\n\nwhere remainder_terms picks out the remainder component of the list returned by the term-list division operation div_terms that was implemented in exercise.\n\nWe can solve the problem exhibited in\nexercise if\nwe use the following modification of the GCD algorithm (which really\nworks only in the case of polynomials with integer coefficients).\n\nBefore performing any polynomial division in the GCD computation, we\nmultiply the dividend by an integer constant factor, chosen to\nguarantee that no fractions will arise during the division process.\n\nOur answer will thus differ from the actual GCD by an integer constant\nfactor, but this does not matter in the case of reducing rational\nfunctions to lowest terms; the GCD will be used to divide both the\nnumerator and denominator, so the integer constant factor will cancel\nout.\n\nMore precisely, if $P$ and\n$Q$ are polynomials, let\n$O_1$ be the order of\n$P$ (i.e., the order of the largest term of\n$P$ ) and let $O_2$\nbe the order of $Q$.\n\nLet\n$c$ be the leading coefficient of\n$Q$.\n\nThen it can be shown that, if we multiply\n$P$ by the\nintegerizing factor\n$c^{1+O_{1} -O_{2}}$ , the resulting polynomial\ncan be divided by $Q$ by using the\ndiv_terms\nalgorithm without introducing any fractions.", - "token_count": 288, - "has_code": true, - "chapter": "Building Abstractions with Data", - "section": "Systems with Generic Operations", - "subsection": "Example: Symbolic Algebra", - "chunk_index": 11, - "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_11" - }, - { - "content": "Then it can be shown that, if we multiply\n$P$ by the\nintegerizing factor\n$c^{1+O_{1} -O_{2}}$ , the resulting polynomial\ncan be divided by $Q$ by using the\ndiv_terms\nalgorithm without introducing any fractions.\n\nThe operation of multiplying\nthe dividend by this constant and then dividing is sometimes called the\npseudodivision of $P$ by\n$Q$.\n\nThe remainder of the division is\ncalled the\npseudoremainder.\n\nThus, here is how to reduce a rational function to lowest terms:\n-\n-\nCompute the GCD of the numerator and denominator, using\nthe version of\ngcd_@terms\nfrom exercise.\n-\n-\nWhen you obtain the GCD, multiply both numerator and\ndenominator by the same integerizing factor before dividing through by\nthe GCD, so that division by the GCD will not introduce any noninteger\ncoefficients.\n\nAs the factor you can use the leading coefficient of\nthe GCD raised to the power\n$1+O_{1} -O_{2}$ , where\n$O_{2}$ is the order of the GCD and\n$O_{1}$ is the maximum of the orders of the\nnumerator and denominator.\n\nThis will ensure that dividing the\nnumerator and denominator by the GCD will not introduce any fractions.\n-\n-\nThe result of this operation will be a numerator and denominator\nwith integer coefficients.\n\nThe coefficients will normally be very\nlarge because of all of the integerizing factors, so the last step is\nto remove the redundant factors by computing the (integer) greatest\ncommon divisor of all the coefficients of the numerator and the\ndenominator and dividing through by this factor.\n\nThe GCD computation is at the heart of any system that does operations\non rational functions.\n\nThe algorithm used above, although\nmathematically straightforward, is extremely slow.\n\nThe slowness is\ndue partly to the large number of division operations and partly to\nthe enormous size of the intermediate coefficients generated by the\npseudodivisions.", + "content": "Then it can be shown that, if we multiply\n$P$ by the\nintegerizing factor\n$c^{1+O_{1} -O_{2}}$ , the resulting polynomial\ncan be divided by $Q$ by using the\ndiv_terms\nalgorithm without introducing any fractions.\n\nThe remainder of the division is\ncalled the\npseudoremainder.\n\nThus, here is how to reduce a rational function to lowest terms:\n-\n-\nCompute the GCD of the numerator and denominator, using\nthe version of\ngcd_@terms\nfrom exercise.\n-\n-\nWhen you obtain the GCD, multiply both numerator and\ndenominator by the same integerizing factor before dividing through by\nthe GCD, so that division by the GCD will not introduce any noninteger\ncoefficients.\n\nAs the factor you can use the leading coefficient of\nthe GCD raised to the power\n$1+O_{1} -O_{2}$ , where\n$O_{2}$ is the order of the GCD and\n$O_{1}$ is the maximum of the orders of the\nnumerator and denominator.\n\nThis will ensure that dividing the\nnumerator and denominator by the GCD will not introduce any fractions.\n-\n-\nThe result of this operation will be a numerator and denominator\nwith integer coefficients.\n\nThe coefficients will normally be very\nlarge because of all of the integerizing factors, so the last step is\nto remove the redundant factors by computing the (integer) greatest\ncommon divisor of all the coefficients of the numerator and the\ndenominator and dividing through by this factor.\n\nThe GCD computation is at the heart of any system that does operations\non rational functions.\n\nThe algorithm used above, although\nmathematically straightforward, is extremely slow.\n\nThe slowness is\ndue partly to the large number of division operations and partly to\nthe enormous size of the intermediate coefficients generated by the\npseudodivisions.\n\nOne of the active areas in the development of\nalgebraic-manipulation systems is the design of better algorithms for\ncomputing polynomial GCDs.", "token_count": 300, "has_code": false, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", "subsection": "Example: Symbolic Algebra", - "chunk_index": 12, - "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_12" + "chunk_index": 11, + "chunk_id": "Building_Abstractions_with_Data_Example_Symbolic_Algebra_11" }, { - "content": "The task of designing generic arithmetic operations is analogous to that of\ndesigning the generic complex-number operations.\n\nWe would like, for\ninstance, to have a generic addition\nfunction\nadd_rat\non rational numbers, and like\nadd_complex\non complex numbers.\n\nWe can implement following the same strategy we\nused in section to implement the\ngeneric selectors for complex numbers.\n\nWe will attach a type tag to each\nkind of number and cause the generic\nfunction\nto dispatch to an appropriate package according to the data type of its\narguments.\n\nThe generic arithmetic functions are defined as follows:\n\n```javascript\nops\n apply_generic\n\nfunction add(x, y) { return apply_generic(\"add\", list(x, y)); }\n\nfunction sub(x, y) { return apply_generic(\"sub\", list(x, y)); }\n\nfunction mul(x, y) { return apply_generic(\"mul\", list(x, y)); }\n\nfunction div(x, y) { return apply_generic(\"div\", list(x, y)); }\n```\n\nWe begin\nby installing a package for handling\nordinary numbers,\nthat is, the primitive numbers of our language.\n\nWe\ntag these\nwith the\nstring \"javascript_number\".\n\nThe arithmetic operations in this package are the primitive arithmetic\nfunctions\n(so there is no need to define extra\nfunctions\nto handle the untagged numbers).\n\nSince these operations each take two\narguments, they are installed in the table keyed by the list\nlist(\"javascript_number\", \"javascript_number\"):\n\n```javascript\ninstall_javascript_number_package\n ops\n operation_table_from_chapter_3\n operation_table\n attach_tag\n\nfunction install_javascript_number_package() {\n function tag(x) {\n return attach_tag(\"javascript_number\", x);\n }\n put(\"add\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x + y));\n put(\"sub\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x - y));\n put(\"mul\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x * y));\n put(\"div\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x / y));\n put(\"make\", \"javascript_number\",\n x => tag(x));\n return \"done\";\n}\n```\n\nUsers of the JavaScript-number package will create (tagged) ordinary numbers by means of the function:\n\n```javascript\nactually_install_javascript_number_package\n\ninstall_javascript_number_package();\n```\n\n```javascript\ninstall_javascript_number_package_usage\n install_javascript_number_package\n actually_install_javascript_number_package\n install_javascript_number_package_usage_example\n [ 'javascript_number', 9 ]\n\nfunction make_javascript_number(n) {\n return get(\"make\", \"javascript_number\")(n);\n}\n```", - "token_count": 301, + "content": "The task of designing generic arithmetic operations is analogous to that of\ndesigning the generic complex-number operations.\n\nWe would like, for\ninstance, to have a generic addition\nfunction\nthat acts like ordinary primitive addition\non ordinary numbers, like\nadd_rat\non rational numbers, and like\nadd_complex\non complex numbers.\n\nWe can implement , and\nthe other generic arithmetic operations, by following the same strategy we\nused in section to implement the\ngeneric selectors for complex numbers.\n\nWe will attach a type tag to each\nkind of number and cause the generic\nfunction\nto dispatch to an appropriate package according to the data type of its\narguments.\n\nThe generic arithmetic functions are defined as follows:\n\n```javascript\nops\n apply_generic\n\nfunction add(x, y) { return apply_generic(\"add\", list(x, y)); }\n\nfunction sub(x, y) { return apply_generic(\"sub\", list(x, y)); }\n\nfunction mul(x, y) { return apply_generic(\"mul\", list(x, y)); }\n\nfunction div(x, y) { return apply_generic(\"div\", list(x, y)); }\n```\n\nWe begin\nby installing a package for handling\nordinary numbers,\nthat is, the primitive numbers of our language.\n\nWe\ntag these\nwith the\nstring \"javascript_number\".\n\nThe arithmetic operations in this package are the primitive arithmetic\nfunctions\n(so there is no need to define extra\nfunctions\nto handle the untagged numbers).\n\nSince these operations each take two\narguments, they are installed in the table keyed by the list\nlist(\"javascript_number\", \"javascript_number\"):\n\n```javascript\ninstall_javascript_number_package\n ops\n operation_table_from_chapter_3\n operation_table\n attach_tag\n\nfunction install_javascript_number_package() {\n function tag(x) {\n return attach_tag(\"javascript_number\", x);\n }\n put(\"add\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x + y));\n put(\"sub\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x - y));\n put(\"mul\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x * y));\n put(\"div\", list(\"javascript_number\", \"javascript_number\"),\n (x, y) => tag(x / y));\n put(\"make\", \"javascript_number\",\n x => tag(x));\n return \"done\";\n}\n```\n\nUsers of the JavaScript-number package will create (tagged) ordinary numbers by means of the function:\n\n```javascript\nactually_install_javascript_number_package\n\ninstall_javascript_number_package();\n```", + "token_count": 302, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -1110,8 +1100,8 @@ "chunk_id": "Building_Abstractions_with_Data_Generic_Arithmetic_Operations_1" }, { - "content": "Users of the JavaScript-number package will create (tagged) ordinary numbers by means of the function:\n\n```javascript\ninstall_javascript_number_package_usage_example\n\nconst n1 = make_javascript_number(4);\nconst n2 = make_javascript_number(5);\n\nadd(n1, n2);\n```\n\nNow that the framework of the generic arithmetic system is in place,\nwe can readily include new kinds of numbers.\n\nHere is a package that\nperforms rational arithmetic.\n\nNotice that, as a benefit of\nadditivity, we can use without modification the rational-number code\nfrom section as the internal\nfunctions\nin the package:\n\n```javascript\nbenefit_of_additivity\n ops\n operation_table_from_chapter_3\n operation_table\n attach_tag\n gcd_definition\n benefit_of_additivity_example\n [ 'rational', [ 11, 15 ] ]\n\nfunction install_rational_package() {\n // internal functions\n function numer(x) { return head(x); }\n function denom(x) { return tail(x); }\n function make_rat(n, d) {\n const g = gcd(n, d);\n return pair(n / g, d / g);\n }\n function add_rat(x, y) {\n return make_rat(numer(x) * denom(y) + numer(y) * denom(x),\n denom(x) * denom(y));\n }\n function sub_rat(x, y) {\n return make_rat(numer(x) * denom(y) - numer(y) * denom(x),\n denom(x) * denom(y));\n }\n function mul_rat(x, y) {\n return make_rat(numer(x) * numer(y),\n denom(x) * denom(y));\n }\n function div_rat(x, y) {\n return make_rat(numer(x) * denom(y),\n denom(x) * numer(y));\n }\n // interface to rest of the system\n function tag(x) {\n return attach_tag(\"rational\", x);\n }\n put(\"add\", list(\"rational\", \"rational\"),\n (x, y) => tag(add_rat(x, y)));\n put(\"sub\", list(\"rational\", \"rational\"),\n (x, y) => tag(sub_rat(x, y)));\n put(\"mul\", list(\"rational\", \"rational\"),\n (x, y) => tag(mul_rat(x, y)));\n put(\"div\", list(\"rational\", \"rational\"),\n (x, y) => tag(div_rat(x, y)));\n put(\"make\", \"rational\",\n (n, d) => tag(make_rat(n, d)));\n return \"done\";\n}\n\nfunction make_rational(n, d) {\n return get(\"make\", \"rational\")(n, d);\n}\n```\n\n```javascript\nbenefit_of_additivity_example\n\ninstall_rational_package();\n\nconst r1 = make_rational(1, 3);\nconst r2 = make_rational(2, 5);\n\nadd(r1, r2);\n```\n\nWe can install a similar package to handle complex numbers, using the tag\n\"complex\".", - "token_count": 282, + "content": "Users of the JavaScript-number package will create (tagged) ordinary numbers by means of the function:\n\n```javascript\ninstall_javascript_number_package_usage_example\n\nconst n1 = make_javascript_number(4);\nconst n2 = make_javascript_number(5);\n\nadd(n1, n2);\n```\n\nNow that the framework of the generic arithmetic system is in place,\nwe can readily include new kinds of numbers.\n\nHere is a package that\nperforms rational arithmetic.\n\nNotice that, as a benefit of\nadditivity, we can use without modification the rational-number code\nfrom section as the internal\nfunctions\nin the package:\n\n```javascript\nbenefit_of_additivity\n ops\n operation_table_from_chapter_3\n operation_table\n attach_tag\n gcd_definition\n benefit_of_additivity_example\n [ 'rational', [ 11, 15 ] ]\n\nfunction install_rational_package() {\n // internal functions\n function numer(x) { return head(x); }\n function denom(x) { return tail(x); }\n function make_rat(n, d) {\n const g = gcd(n, d);\n return pair(n / g, d / g);\n }\n function add_rat(x, y) {\n return make_rat(numer(x) * denom(y) + numer(y) * denom(x),\n denom(x) * denom(y));\n }\n function sub_rat(x, y) {\n return make_rat(numer(x) * denom(y) - numer(y) * denom(x),\n denom(x) * denom(y));\n }\n function mul_rat(x, y) {\n return make_rat(numer(x) * numer(y),\n denom(x) * denom(y));\n }\n function div_rat(x, y) {\n return make_rat(numer(x) * denom(y),\n denom(x) * numer(y));\n }\n // interface to rest of the system\n function tag(x) {\n return attach_tag(\"rational\", x);\n }\n put(\"add\", list(\"rational\", \"rational\"),\n (x, y) => tag(add_rat(x, y)));\n put(\"sub\", list(\"rational\", \"rational\"),\n (x, y) => tag(sub_rat(x, y)));\n put(\"mul\", list(\"rational\", \"rational\"),\n (x, y) => tag(mul_rat(x, y)));\n put(\"div\", list(\"rational\", \"rational\"),\n (x, y) => tag(div_rat(x, y)));\n put(\"make\", \"rational\",\n (n, d) => tag(make_rat(n, d)));\n return \"done\";\n}\n\nfunction make_rational(n, d) {\n return get(\"make\", \"rational\")(n, d);\n}\n```\n\n```javascript\nbenefit_of_additivity_example\n\ninstall_rational_package();\n\nconst r1 = make_rational(1, 3);\nconst r2 = make_rational(2, 5);\n\nadd(r1, r2);\n```\n\nWe can install a similar package to handle complex numbers, using the tag\n\"complex\".\n\nIn creating the package, we extract from the table the operations\nmake_from_real_imag\nand\nmake_from_mag_ang\nthat were defined by the rectangular and polar packages.", + "token_count": 305, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -1120,8 +1110,8 @@ "chunk_id": "Building_Abstractions_with_Data_Generic_Arithmetic_Operations_2" }, { - "content": "We can install a similar package to handle complex numbers, using the tag\n\"complex\".\n\nIn creating the package, we extract from the table the operations\nmake_from_real_imag\nand\nmake_from_mag_ang\nthat were defined by the rectangular and polar packages.\nadd_complex,\nsub_complex,\nmul_complex,\nand\ndiv_complex\nfunctions\nfrom section.\n\n```javascript\ninstall_complex_package\n ops\n generic_selectors\n operation_table_from_chapter_3\n operation_table\n install_rectangular_package\n install_rectangular_package_usage\n install_polar_package_usage\n attach_tag\n actually_install_complex_package\n 'done'\n\nfunction install_complex_package() {\n // imported functions from rectangular and polar packages\n function make_from_real_imag(x, y) {\n return get(\"make_from_real_imag\", \"rectangular\")(x, y);\n }\n function make_from_mag_ang(r, a) {\n return get(\"make_from_mag_ang\", \"polar\")(r, a);\n }\n // internal functions\n function add_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) + real_part(z2),\n imag_part(z1) + imag_part(z2));\n }\n function sub_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) - real_part(z2),\n imag_part(z1) - imag_part(z2));\n }\n function mul_complex(z1, z2) {\n return make_from_mag_ang(magnitude(z1) * magnitude(z2),\n angle(z1) + angle(z2));\n }\n function div_complex(z1, z2) {\n return make_from_mag_ang(magnitude(z1) / magnitude(z2),\n angle(z1) - angle(z2));\n }\n // interface to rest of the system\n function tag(z) { return attach_tag(\"complex\", z); }\n put(\"add\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(add_complex(z1, z2)));\n put(\"sub\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(sub_complex(z1, z2)));\n put(\"mul\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(mul_complex(z1, z2)));\n put(\"div\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(div_complex(z1, z2)));\n put(\"make_from_real_imag\", \"complex\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"complex\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\n```\n\nPrograms outside the complex-number package can construct complex\nnumbers either from real and imaginary parts or from magnitudes and\nangles.\n\nNotice how the underlying\nfunctions,\noriginally defined in the rectangular and polar packages, are exported to\nthe complex package, and exported from there to the outside world.\n\n```javascript\nactually_install_complex_package\n\ninstall_complex_package();\n```\n\n```javascript\ninstall_complex_package_usage\n install_complex_package\n actually_install_complex_package\n [ 'rectangular', [ 8.387912809451864, 5.397127693021015 ] ]\n install_complex_package_example\n\nfunction make_complex_from_real_imag(x, y){\n return get(\"make_from_real_imag\", \"complex\")(x, y);\n}\nfunction make_complex_from_mag_ang(r, a){\n return get(\"make_from_mag_ang\", \"complex\")(r, a);\n}\n```", - "token_count": 281, + "content": "In creating the package, we extract from the table the operations\nmake_from_real_imag\nand\nmake_from_mag_ang\nthat were defined by the rectangular and polar packages.\n\n```javascript\ninstall_complex_package\n ops\n generic_selectors\n operation_table_from_chapter_3\n operation_table\n install_rectangular_package\n install_rectangular_package_usage\n install_polar_package_usage\n attach_tag\n actually_install_complex_package\n 'done'\n\nfunction install_complex_package() {\n // imported functions from rectangular and polar packages\n function make_from_real_imag(x, y) {\n return get(\"make_from_real_imag\", \"rectangular\")(x, y);\n }\n function make_from_mag_ang(r, a) {\n return get(\"make_from_mag_ang\", \"polar\")(r, a);\n }\n // internal functions\n function add_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) + real_part(z2),\n imag_part(z1) + imag_part(z2));\n }\n function sub_complex(z1, z2) {\n return make_from_real_imag(real_part(z1) - real_part(z2),\n imag_part(z1) - imag_part(z2));\n }\n function mul_complex(z1, z2) {\n return make_from_mag_ang(magnitude(z1) * magnitude(z2),\n angle(z1) + angle(z2));\n }\n function div_complex(z1, z2) {\n return make_from_mag_ang(magnitude(z1) / magnitude(z2),\n angle(z1) - angle(z2));\n }\n // interface to rest of the system\n function tag(z) { return attach_tag(\"complex\", z); }\n put(\"add\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(add_complex(z1, z2)));\n put(\"sub\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(sub_complex(z1, z2)));\n put(\"mul\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(mul_complex(z1, z2)));\n put(\"div\", list(\"complex\", \"complex\"),\n (z1, z2) => tag(div_complex(z1, z2)));\n put(\"make_from_real_imag\", \"complex\",\n (x, y) => tag(make_from_real_imag(x, y)));\n put(\"make_from_mag_ang\", \"complex\",\n (r, a) => tag(make_from_mag_ang(r, a)));\n return \"done\";\n}\n```\n\nPrograms outside the complex-number package can construct complex\nnumbers either from real and imaginary parts or from magnitudes and\nangles.\n\nNotice how the underlying\nfunctions,\noriginally defined in the rectangular and polar packages, are exported to\nthe complex package, and exported from there to the outside world.\n\n```javascript\nactually_install_complex_package\n\ninstall_complex_package();\n```\n\n```javascript\ninstall_complex_package_usage\n install_complex_package\n actually_install_complex_package\n [ 'rectangular', [ 8.387912809451864, 5.397127693021015 ] ]\n install_complex_package_example\n\nfunction make_complex_from_real_imag(x, y){\n return get(\"make_from_real_imag\", \"complex\")(x, y);\n}\nfunction make_complex_from_mag_ang(r, a){\n return get(\"make_from_mag_ang\", \"complex\")(r, a);\n}\n```\n\n```javascript\ninstall_complex_package_example\n\nconst r = make_complex_from_real_imag(4, 3);\nconst p = make_complex_from_mag_ang(5, 0.5);\n\nadd(r, p); // results in a complex number in rectangular coordinates\n// mul(r, p); // results in a complex number in polar coordinates\n\nconst r = make_complex_from_real_imag(4, 3);\nconst p = make_complex_from_mag_ang(5, 0.5);\ntail(add(r, p));\n```", + "token_count": 307, "has_code": true, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", @@ -1130,9 +1120,9 @@ "chunk_id": "Building_Abstractions_with_Data_Generic_Arithmetic_Operations_3" }, { - "content": "Notice how the underlying\nfunctions,\noriginally defined in the rectangular and polar packages, are exported to\nthe complex package, and exported from there to the outside world.\n\n```javascript\ninstall_complex_package_example\n\nconst r = make_complex_from_real_imag(4, 3);\nconst p = make_complex_from_mag_ang(5, 0.5);\n\nadd(r, p); // results in a complex number in rectangular coordinates\n// mul(r, p); // results in a complex number in polar coordinates\n\nconst r = make_complex_from_real_imag(4, 3);\nconst p = make_complex_from_mag_ang(5, 0.5);\ntail(add(r, p));\n```\n\nWhat we have here is a\n$3+4i$ in rectangular form, would be\nrepresented as shown in\nfigure.\n\nThe outer tag\n(\"complex\")\nis used to direct the number to the complex package.\n\nOnce within the\ncomplex package, the next tag\n(\"rectangular\")\nis used to direct the number to the rectangular package.\n\nIn a large and\ncomplicated system there might be many levels, each interfaced with the\nnext by means of generic operations.\n\nAs a data object is passed\ndownward, the outer tag that is used to direct it to the\nappropriate package is stripped off (by applying\n\n```javascript\nRepresentation of $3+4i$ in\n\t rectangular form.\n```\n\nIn the above packages, we used\nadd_rat,\nadd_complex,\nand the other arithmetic\nfunctions\nexactly as originally written.\n\nOnce these declarations are internal to\ndifferent installation\nfunctions,\nhowever, they no longer need names that are distinct from each other:\nwe could simply name them", - "token_count": 222, - "has_code": true, + "content": "Notice how the underlying\nfunctions,\noriginally defined in the rectangular and polar packages, are exported to\nthe complex package, and exported from there to the outside world.\n\nA typical complex number,\nsuch as $3+4i$ in rectangular form, would be\nrepresented as shown in\nfigure.\n\nThe outer tag\n(\"complex\")\nis used to direct the number to the complex package.\n\nOnce within the\ncomplex package, the next tag\n(\"rectangular\")\nis used to direct the number to the rectangular package.\n\nIn a large and\ncomplicated system there might be many levels, each interfaced with the\nnext by means of generic operations.\n\nAs a data object is passed\ndownward, the outer tag that is used to direct it to the\nappropriate package is stripped off (by applying\n) and the next level of tag (if any)\nbecomes visible to be used for further dispatching.\n\nRepresentation of $3+4i$ in rectangular form.\n\nIn the above packages, we used\nadd_rat,\nadd_complex,\nand the other arithmetic\nfunctions\nexactly as originally written.\n\nOnce these declarations are internal to\ndifferent installation\nfunctions,\nhowever, they no longer need names that are distinct from each other:\nwe could simply name them ,\n, , and\nin both packages.", + "token_count": 196, + "has_code": false, "chapter": "Building Abstractions with Data", "section": "Systems with Generic Operations", "subsection": "Generic Arithmetic Operations", @@ -1140,8 +1130,8 @@ "chunk_id": "Building_Abstractions_with_Data_Generic_Arithmetic_Operations_4" }, { - "content": "We are about to study the idea of a\ncomputational process.\ndata.\nprogram.\n\nPeople create programs to direct processes.\n\nIn effect, we conjure the spirits of the computer with our spells.\n\nA computational process is indeed much like a sorcerer s idea of a\nspirit.\n\nIt cannot be seen or touched.\n\nIt is not composed of matter\nat all.\n\nHowever, it is very real.\n\nIt can perform intellectual work.\n\nIt can answer questions.\n\nIt can affect the world by disbursing money\nat a bank or by controlling a robot arm in a factory.\n\nThe programs we\nuse to conjure processes are like a sorcerer s spells.\n\nThey are\ncarefully composed from symbolic expressions in arcane and esoteric\nprogramming languages\n\nA computational process, in a correctly working computer, executes\nprograms precisely and accurately.\n\nThus, like the sorcerer s\napprentice, novice programmers must learn to understand and to\nanticipate the consequences of their conjuring.\n\nEven small errors\n(usually called bugs)\nin programs can have complex and unanticipated consequences.\n\nFortunately, learning to program is considerably less dangerous than\nlearning sorcery, because the spirits we deal with are conveniently\ncontained in a secure way.\n\nReal-world programming, however,\nrequires care, expertise, and wisdom.\n\nA small bug in a computer-aided\ndesign program, for example, can lead to the catastrophic collapse of\nan airplane or a dam or the self-destruction of an industrial robot.\n\nMaster software engineers have the ability to organize programs so\nthat they can be reasonably sure that the resulting processes will\nperform the tasks intended.\n\nThey can visualize the behavior of their\nsystems in advance.\n\nThey know how to structure programs so that\nunanticipated problems do not lead to catastrophic consequences, and\nwhen problems do arise, they can\ndebug\ntheir programs.", - "token_count": 288, + "content": "We are about to study the idea of a\ncomputational process.\n\nComputational processes are abstract beings that inhabit computers.\n\nAs they evolve, processes manipulate other abstract things called\ndata.\n\nThe evolution of a process is directed by a pattern of rules\ncalled a\nprogram.\n\nPeople create programs to direct processes.\n\nIn effect, we conjure the spirits of the computer with our spells.\n\nA computational process is indeed much like a sorcerer s idea of a\nspirit.\n\nIt cannot be seen or touched.\n\nIt is not composed of matter\nat all.\n\nHowever, it is very real.\n\nIt can perform intellectual work.\n\nIt can answer questions.\n\nIt can affect the world by disbursing money\nat a bank or by controlling a robot arm in a factory.\n\nThe programs we\nuse to conjure processes are like a sorcerer s spells.\n\nThey are\ncarefully composed from symbolic expressions in arcane and esoteric\nprogramming languages\nthat prescribe the tasks we want our\nprocesses to perform.\n\nA computational process, in a correctly working computer, executes\nprograms precisely and accurately.\n\nThus, like the sorcerer s\napprentice, novice programmers must learn to understand and to\nanticipate the consequences of their conjuring.\n\nEven small errors\n(usually called bugs)\nin programs can have complex and unanticipated consequences.\n\nFortunately, learning to program is considerably less dangerous than\nlearning sorcery, because the spirits we deal with are conveniently\ncontained in a secure way.\n\nReal-world programming, however,\nrequires care, expertise, and wisdom.\n\nA small bug in a computer-aided\ndesign program, for example, can lead to the catastrophic collapse of\nan airplane or a dam or the self-destruction of an industrial robot.\n\nMaster software engineers have the ability to organize programs so\nthat they can be reasonably sure that the resulting processes will\nperform the tasks intended.", + "token_count": 293, "has_code": false, "chapter": "Building Abstractions with Functions", "section": null, @@ -1150,8 +1140,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Building_Abstractions_with_Functions_1" }, { - "content": "They know how to structure programs so that\nunanticipated problems do not lead to catastrophic consequences, and\nwhen problems do arise, they can\ndebug\ntheir programs.\n\nWell-designed\ncomputational systems, like well-designed automobiles or nuclear\nreactors, are designed in a modular manner, so that the parts can be\nconstructed, replaced, and debugged separately.\n\nWe need an appropriate language for describing processes, and we will\nuse for this purpose the programming language JavaScript.\n\nJust as our\neveryday thoughts are usually expressed in our natural language (such\nas English, Swedish, or Chinese), and descriptions of quantitative\nphenomena are expressed with mathematical notations, our procedural\nthoughts will be expressed in JavaScript.\n\nMocha , which\nwas later renamed to LiveScript , and finally to JavaScript.\n\nJavaScript is a trademark\nof Oracle Corporation.\n\nDespite its inception as a language for scripting the web, JavaScript interpreter is a machine that carries out processes described in the JavaScript language.\n\nJavaScript bears only superficial resemblance to the language Java,\nafter which it was\n(eventually) named; both Java and JavaScript use the block structure of\nthe language C.\n\nIn contrast with Java and C, which usually\nemploy compilation to lower-level\nlanguages, JavaScript programs were initially\ninterpreted\nby web browsers.\ns Internet Explorer, whose\nJavaScript version is called\nJScript.\n\nThe popularity of JavaScript for controlling web\nbrowsers gave rise to a standardization effort, culminating in\nECMAScript.\n\nThe\nand completed in June 1997\n(\n\nThe practice of embedding JavaScript programs in web pages encouraged\nthe developers of web browsers to implement JavaScript interpreters.\n\nAs these programs became more complex,\nthe interpreters became more efficient in executing them, eventually\nusing sophisticated implementation techniques such as Just-In-Time\n(JIT) compilation.", - "token_count": 275, + "content": "Master software engineers have the ability to organize programs so\nthat they can be reasonably sure that the resulting processes will\nperform the tasks intended.\n\nThey know how to structure programs so that\nunanticipated problems do not lead to catastrophic consequences, and\nwhen problems do arise, they can\ndebug\ntheir programs.\n\nWell-designed\ncomputational systems, like well-designed automobiles or nuclear\nreactors, are designed in a modular manner, so that the parts can be\nconstructed, replaced, and debugged separately.\n\nWe need an appropriate language for describing processes, and we will\nuse for this purpose the programming language JavaScript.\n\nJust as our\neveryday thoughts are usually expressed in our natural language (such\nas English, Swedish, or Chinese), and descriptions of quantitative\nphenomena are expressed with mathematical notations, our procedural\nthoughts will be expressed in JavaScript.\n\nJavaScript was developed in 1995\nas a programming language for controlling the behavior\nof World Wide Web browsers through scripts that are embedded\nin web pages.\n\nThe language was conceived by\nBrendan Eich, originally under the name Mocha , which\nwas later renamed to LiveScript , and finally to JavaScript.\n\nThe name JavaScript is a trademark\nof Oracle Corporation.\n\nDespite its inception as a language for scripting the web, JavaScript\nis a general-purpose programming language.\n\nA JavaScript\ninterpreter is a machine that carries out processes described\nin the JavaScript language.\n\nThe first JavaScript interpreter was implemented by Eich\nat Netscape Communications Corporation for the Netscape Navigator web\nbrowser.\n\nJavaScript inherited its core features from the and Self programming languages.\n\nis a dialect of , and\nwas used as the programming language for the original version of this\nbook.\n\nFrom , JavaScript inherited its most fundamental design\nprinciples,\nsuch as lexically scoped first-class\nfunctions and dynamic typing.", + "token_count": 288, "has_code": false, "chapter": "Building Abstractions with Functions", "section": null, @@ -1160,8 +1150,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Building_Abstractions_with_Functions_2" }, { - "content": "As these programs became more complex,\nthe interpreters became more efficient in executing them, eventually\nusing sophisticated implementation techniques such as Just-In-Time\n(JIT) compilation.\n\nThe majority of JavaScript programs as of this writing (2021) are embedded\nin web pages and interpreted by browsers, but JavaScript is increasingly\nused as a general-purpose programming language, using systems such as\nNode.js.", - "token_count": 58, + "content": "From , JavaScript inherited its most fundamental design\nprinciples,\nsuch as lexically scoped first-class\nfunctions and dynamic typing.\n\nIn contrast with Java and C, which usually\nemploy compilation to lower-level\nlanguages, JavaScript programs were initially\ninterpreted\nby web browsers.\n\nAfter Netscape Navigator, other web browsers provided interpreters\nfor the language, including Microsoft s Internet Explorer, whose\nJavaScript version is called\nJScript.\n\nThe popularity of JavaScript for controlling web\nbrowsers gave rise to a standardization effort, culminating in\nECMAScript.\n\nThe\nfirst edition of the ECMAScript standard was led by Guy Lewis\nSteele Jr. and completed in June 1997\n().\n\nThe sixth edition, known as ECMAScript 2015, was led by\nAllen Wirfs-Brock and adopted by the General Assembly of ECMA in\nJune 2015 ().\n\nThe practice of embedding JavaScript programs in web pages encouraged\nthe developers of web browsers to implement JavaScript interpreters.\n\nAs these programs became more complex,\nthe interpreters became more efficient in executing them, eventually\nusing sophisticated implementation techniques such as Just-In-Time\n(JIT) compilation.\n\nThe majority of JavaScript programs as of this writing (2021) are embedded\nin web pages and interpreted by browsers, but JavaScript is increasingly\nused as a general-purpose programming language, using systems such as\nNode.js.\n\nECMAScript 2015 possesses a set of features that make it an excellent\nmedium for studying important programming constructs and data\nstructures and for relating them to the linguistic features that\nsupport them.\n\nIts\nlexically scoped first-class functions and their syntactic support\nthrough lambda expressions provide direct and concise access to\nfunctional abstraction, and\ndynamic typing allows the adaptation to remain close to the original throughout the book.\n\nAbove and beyond these\nconsiderations, programming in JavaScript is great fun.", + "token_count": 277, "has_code": false, "chapter": "Building Abstractions with Functions", "section": null, @@ -1170,8 +1160,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Building_Abstractions_with_Functions_3" }, { - "content": "We have seen that\nfunctions\nare, in effect, abstractions that describe compound operations on\nnumbers independent of the particular numbers.\n\nFor example, when we\ndeclare\n\n```javascript\ncube_definition\n cube_example\n\nfunction cube(x) {\n return x * x * x;\n}\n```\n\n```javascript\ncube_example\n\t 27\n cube_definition\n\ncube(3);\n```\n\nwe are not talking about the cube of a particular number, but rather\nabout a method for obtaining the cube of any number.\n\nOf course we could\nget along without ever\ndeclaring this function,\nby always writing expressions such as\n\n```javascript\n3 * 3 * 3\nx * x * x\ny * y * y\n```\n\nand never mentioning\nFunctions\nprovide this ability.\n\nThis is why all but the most primitive\nprogramming languages include mechanisms for\ndeclaring functions.\n\nYet even in numerical processing we will be severely limited in our\nability to create abstractions if we are restricted to\nfunctions\nwhose parameters must be numbers.\n\nOften the same programming pattern\nwill be used with a number of different\nfunctions.\n\nTo express such patterns as concepts, we will need to construct\nfunctions\nthat can accept\nfunctions\nas arguments or return\nfunctions\nas values.\n\nFunctions\nthat manipulate\nfunctions\nare called\nhigher-order functions.\n\nThis section shows how higher-order\nfunctions\ncan serve as powerful abstraction mechanisms, vastly increasing the\nexpressive power of our language.", - "token_count": 216, + "content": "We have seen that\nfunctions\nare, in effect, abstractions that describe compound operations on\nnumbers independent of the particular numbers.\n\nFor example, when we\ndeclare\n\n```javascript\ncube_definition\n cube_example\n\nfunction cube(x) {\n return x * x * x;\n}\n```\n\n```javascript\ncube_example\n\t 27\n cube_definition\n\ncube(3);\n```\n\nwe are not talking about the cube of a particular number, but rather\nabout a method for obtaining the cube of any number.\n\nOf course we could\nget along without ever\ndeclaring this function,\nby always writing expressions such as\n\n```javascript\n3 * 3 * 3\nx * x * x\ny * y * y\n```\n\nand never mentioning explicitly.\n\nThis\nwould place us at a serious disadvantage, forcing us to work always at\nthe level of the particular operations that happen to be primitives in\nthe language (multiplication, in this case) rather than in terms of\nhigher-level operations.\n\nOur programs would be able to compute cubes,\nbut our language would lack the ability to express the concept of cubing.\n\nOne of the things we should demand from a powerful programming language\nis the ability to build abstractions by assigning names to common\npatterns and then to work in terms of the abstractions directly.\n\nFunctions\nprovide this ability.\n\nThis is why all but the most primitive\nprogramming languages include mechanisms for\ndeclaring functions.\n\nYet even in numerical processing we will be severely limited in our\nability to create abstractions if we are restricted to\nfunctions\nwhose parameters must be numbers.\n\nOften the same programming pattern\nwill be used with a number of different\nfunctions.\n\nTo express such patterns as concepts, we will need to construct\nfunctions\nthat can accept\nfunctions\nas arguments or return\nfunctions\nas values.\n\nFunctions\nthat manipulate\nfunctions\nare called\nhigher-order functions.", + "token_count": 291, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1180,8 +1170,18 @@ "chunk_id": "Building_Abstractions_with_Functions_Formulating_Abstractions_with_Higher-Order_Functions_1" }, { - "content": "```javascript\nIn using sum as in\n\tsection, it seems\n\tterribly awkward to have to declare trivial functions such as\n pi_term and\n\tpi_next just so we can use them as\n\targuments to our higher-order function. Rather than declare\n\tpi_next and\n pi_term, it would be more convenient\n to have a way to directly specify the function that returns its\n input incremented by 4 and the function that returns the\n reciprocal of its input times its input plus 2. We can do this\n\tby introducing the lambda expression as a syntactic form for\n\tcreating functions.\n\tUsing lambda expressions, we can describe what we want as\n\nx => x + 4\n\n and\n\nx => 1 / (x * (x + 2))\n```\n\n```javascript\nThen we can express our\n\tpi_sum\n\tfunction\n```\n\nwithout declaring any auxiliary functions:\n\n```javascript\npi_sum_definition3\n 3.139592655589783\n sum_definition\n pi_sum_example\n 3.139592655589783\n\nfunction pi_sum(a, b) {\n return sum(x => 1 / (x * (x + 2)),\n a,\n x => x + 4,\n b);\n}\n```\n\nAgain using a lambda expression, we can write the function without having to declare the auxiliary function add_dx:\n\n```javascript\nintegral_example_2\n\nintegral(cube, 0, 1, 0.01);\n```\n\n```javascript\nintegral_definition2\n sum_definition\n cube_definition\n integral_example_2\n 0.24998750000000042\n\nfunction integral(f, a, b, dx) {\n return sum(f,\n a + dx / 2,\n x => x + dx,\n b)\n *\n dx;\n}\n```\n\n```javascript\nIn general, lambda expressions are used to create functions in the\n\tsame way as function declarations,\n\treturn keyword and braces are omitted\n\t(if there is only one\n\tparameter, the\n\n(parameters) => expression\n\n\tThe resulting function is just as much a function\n\tas one that is created using a function declaration statement.\n```\n\nWe consider\n\n```javascript\nplus4_definition_1\n plus4_example\n\nfunction plus4(x) {\n return x + 4;\n}\n```\n\n```javascript\nplus4_example\n\nplus4(3);\n```\n\n```javascript\nto be\n\tequivalent to\n```\n\n```javascript\nplus4_definition_2\n plus4_example\n\nconst plus4 = x => x + 4;\n```", - "token_count": 304, + "content": "Functions\nthat manipulate\nfunctions\nare called\nhigher-order functions.", + "token_count": 8, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Formulating_Abstractions_with_Higher-Order_Functions_2" + }, + { + "content": "In using sum as in section, it seems terribly awkward to have to declare trivial functions such as pi_term and pi_next just so we can use them as arguments to our higher-order function.\n\nRather than declare pi_next and pi_term, it would be more convenient to have a way to directly specify the function that returns its input incremented by 4 and the function that returns the reciprocal of its input times its input plus 2.\n\nWe can do this by introducing the lambda expression as a syntactic form for creating functions.\n\nUsing lambda expressions, we can describe what we want as x => x + 4 and x => 1 / (x * (x + 2))\nThen we can express our pi_sum function\nwithout\ndeclaring any auxiliary functions:\n\n```javascript\npi_sum_definition3\n 3.139592655589783\n sum_definition\n pi_sum_example\n 3.139592655589783\n\nfunction pi_sum(a, b) {\n return sum(x => 1 / (x * (x + 2)),\n a,\n x => x + 4,\n b);\n}\n```\n\nAgain using a lambda expression, we can write the function without having to declare the auxiliary function add_dx:\n\n```javascript\nintegral_example_2\n\nintegral(cube, 0, 1, 0.01);\n```\n\n```javascript\nintegral_definition2\n sum_definition\n cube_definition\n integral_example_2\n 0.24998750000000042\n\nfunction integral(f, a, b, dx) {\n return sum(f,\n a + dx / 2,\n x => x + dx,\n b)\n *\n dx;\n}\n```\n\nIn general, lambda expressions are used to create functions in the same way as function declarations, except that no name is specified for the function and the return keyword and braces are omitted (if there is only one parameter, the parentheses around the parameter list can also be omitted, as in the examples we have seen).\n\n(parameters) => expression The resulting function is just as much a function as one that is created using a function declaration statement.\n\nThe only difference is that it has not been associated with any name in the environment.", + "token_count": 305, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1190,8 +1190,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Constructing_Functions_using_Lambda_Expressions_1" }, { - "content": "We consider\n\n```javascript\nWe can read a lambda expression as follows:\n\n\\begin{flushleft}\\normalcodesize\n\\begin{tabular}{@{}ccccc}\n \\tt x & \\tt => & \\tt x & \\tt + & \\tt 4 \\\\\n $\\Big\\uparrow$ & $\\Big\\uparrow$ & $\\Big\\uparrow$ & $\\Big\\uparrow$ & $\\Big\\uparrow$ \\\\[4pt]\n \\normalsize The function of an argument \\small\\tt x & \\normalsize that results in & \\normalsize the value & \\normalsize plus & \\normalsize 4. \\\\\n\\end{tabular}\n\\end{flushleft}\n```\n\nLike any expression that has a function as its value, a lambda expression can be used as the function expression in an application such as\n\n```javascript\nsquare_definition\n 12\n\n((x, y, z) => x + y + square(z))(1, 2, 3);\n```\n\nor, more generally, in any context where we would normally use a function name.\n\n```javascript\nNote that =>\n has\n```\n\nAnother use of lambda expressions is in creating local names.\n\n```javascript\nWe often need local names in our functions\n\tother than those that have been bound as parameters.\n```\n\nFor example, suppose we wish to compute the function\n\\[\\begin{array}{lll}\nf(x, y)&=&x(1 + x y)^2 +y (1 - y) + (1 + x y)(1 - y)\n\\end{array}\\]\nwhich we could also express as\n\\[\\begin{array}{rll}\na &=& 1+xy\\\\\nb &=& 1-y\\\\\nf(x, y) &= &x a^2 +y b + a b\n\\end{array}\\]\nIn writing a\nfunction\nto compute $f$ , we would like to include as\nlocal names\nnot only $x$ and $y$\nbut also the names of intermediate quantities like\n$a$ and $b$.\n\nOne way\nto accomplish this is to use an auxiliary\nfunction to bind the local names:\n\n```javascript\nf_helper_definition\n square_definition\n f_helper_example\n 456\n\nfunction f(x, y) {\n function f_helper(a, b) {\n return x * square(a) + y * b + a * b;\n }\n return f_helper(1 + x * y, 1 - y);\n}\n```\n\n```javascript\nf_helper_example\n\nf(3, 4);\n```", - "token_count": 295, + "content": "The only difference is that it has not been associated with any name in the environment.\n\n```javascript\nplus4_definition_1\n plus4_example\n\nfunction plus4(x) {\n return x + 4;\n}\n```\n\n```javascript\nplus4_example\n\nplus4(3);\n```\n\nto be equivalent to\n\n```javascript\nplus4_definition_2\n plus4_example\n\nconst plus4 = x => x + 4;\n```\n\nWe can read a lambda expression as follows: \\begin{flushleft}\\normalcodesize \\begin{tabular}{@{}ccccc} \\tt x & \\tt => & \\tt x & \\tt + & \\tt 4 \\\\\n\n$\\Big\\uparrow$ & $\\Big\\uparrow$ & $\\Big\\uparrow$ & $\\Big\\uparrow$ & $\\Big\\uparrow$ \\\\[4pt] \\normalsize The function of an argument \\small\\tt x & \\normalsize that results in & \\normalsize\n\nthe value & \\normalsize plus & \\normalsize 4. \\\\ \\end{tabular} \\end{flushleft}\n\nLike any expression that has a function as its value, a lambda expression can be used as the function expression in an application such as\n\n```javascript\nsquare_definition\n 12\n\n((x, y, z) => x + y + square(z))(1, 2, 3);\n\n12\n```\n\nor, more generally, in any context where we would normally use a\nfunction\nname.\n\nNote that => has lower precedence than function application and thus the parentheses around the lambda expression are necessary here.\n\nAnother use of\nlambda expressions is in creating local names.\n\nWe often need local names in our functions other than those that have been bound as parameters.\n\nFor example, suppose we wish to compute the function\n\\[\\begin{array}{lll}\nf(x, y)&=&x(1 + x y)^2 +y (1 - y) + (1 + x y)(1 - y)\n\\end{array}\\]\nwhich we could also express as\n\\[\\begin{array}{rll}\na &=& 1+xy\\\\\nb &=& 1-y\\\\\nf(x, y) &= &x a^2 +y b + a b\n\\end{array}\\]\nIn writing a\nfunction\nto compute $f$ , we would like to include as\nlocal names\nnot only $x$ and $y$\nbut also the names of intermediate quantities like\n$a$ and $b$.", + "token_count": 294, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1200,8 +1200,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Constructing_Functions_using_Lambda_Expressions_2" }, { - "content": "One way\nto accomplish this is to use an auxiliary\nfunction to bind the local names:\n\nOf course, we could use a\nlambda\nexpression to specify an anonymous\nfunction for binding our local names.\n\nThe\nfunction body\nthen becomes a single call to that\nfunction:\n\n```javascript\nf_helper_definition2\n square_definition\n f_2_helper_example\n 456\n\nfunction f_2(x, y) {\n return ( (a, b) => x * square(a) + y * b + a * b\n )(1 + x * y, 1 - y);\n}\n```\n\n```javascript\nf_2_helper_example\n\nf_2(3, 4);\n```\n\n```javascript\nconst, the function\n\n\tcan be written as\n\n f_helper_definition3\n square_definition\n f_3_helper_example\n\t 456\n\nfunction f_3(x, y) {\n const a = 1 + x * y;\n const b = 1 - y;\n return x * square(a) + y * b + a * b;\n}\n\n\t f_3_helper_example\n\nf_3(3, 4);\n\n Names that are declared with\n const inside a block have the\n body of the immediately surrounding block as their scope.$^,$\n```\n\nConditional statements\nWe have seen that it is often useful to declare names that are local to\nfunction declarations.\n\nWhen functions become big, we should\nkeep the scope of the names as narrow as possible.\n\nConsider for example expmod in\nexercise.\n\n```javascript\nexpmod_definition_2\n even_definition\n expmod_example_2\n\t4\n\nfunction expmod(base, exp, m) {\n return exp === 0\n ? 1\n : is_even(exp)\n ? ( expmod(base, exp / 2, m)\n * expmod(base, exp / 2, m)) % m\n : (base * expmod(base, exp - 1, m)) % m;\n}\n```\n\nThis function is unnecessarily inefficient, because it contains two identical calls:\n\n```javascript\nexpmod_example_2\n even_definition\n expmod_definition_2\n\nexpmod(base, exp / 2, m);\n\nexpmod(4, 3, 5);\n```\n\nWhile this can be easily fixed in this example using the\nsquare function, this is not so easy\nin general.\n\nWithout using square ,\nwe would be tempted to introduce a local name for the expression as\nfollows:", - "token_count": 300, + "content": "For example, suppose we wish to compute the function\n\\[\\begin{array}{lll}\nf(x, y)&=&x(1 + x y)^2 +y (1 - y) + (1 + x y)(1 - y)\n\\end{array}\\]\nwhich we could also express as\n\\[\\begin{array}{rll}\na &=& 1+xy\\\\\nb &=& 1-y\\\\\nf(x, y) &= &x a^2 +y b + a b\n\\end{array}\\]\nIn writing a\nfunction\nto compute $f$ , we would like to include as\nlocal names\nnot only $x$ and $y$\nbut also the names of intermediate quantities like\n$a$ and $b$.\n\n```javascript\nf_helper_definition\n square_definition\n f_helper_example\n 456\n\nfunction f(x, y) {\n function f_helper(a, b) {\n return x * square(a) + y * b + a * b;\n }\n return f_helper(1 + x * y, 1 - y);\n}\n```\n\n```javascript\nf_helper_example\n\nf(3, 4);\n```\n\nOf course, we could use a\nlambda\nexpression to specify an anonymous\nfunction for binding our local names.\n\nThe\nfunction body\nthen becomes a single call to that\nfunction:\n\n```javascript\nf_helper_definition2\n square_definition\n f_2_helper_example\n 456\n\nfunction f_2(x, y) {\n return ( (a, b) => x * square(a) + y * b + a * b\n )(1 + x * y, 1 - y);\n}\n```\n\n```javascript\nf_2_helper_example\n\nf_2(3, 4);\n```\n\nA more convenient way to declare local names is by using constant declarations within the body of the function.\n\nUsing const, the function can be written as f_helper_definition3 square_definition f_3_helper_example 456 function f_3(x, y) { const a = 1 + x * y; const b = 1 - y; return x * square(a) + y * b + a * b; } f_3_helper_example f_3(3, 4); Names that are declared with const inside a block have the body of the immediately surrounding block as their scope.$^,$\n\nConditional statements\nWe have seen that it is often useful to declare names that are local to\nfunction declarations.", + "token_count": 298, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1210,8 +1210,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Constructing_Functions_using_Lambda_Expressions_3" }, { - "content": "Without using square ,\nwe would be tempted to introduce a local name for the expression as\nfollows:\n\n```javascript\neven_definition\n expmod_example\n\nfunction expmod(base, exp, m) {\n const half_exp = expmod(base, exp / 2, m);\n return exp === 0\n ? 1\n : is_even(exp)\n ? (half_exp * half_exp) % m\n : (base * expmod(base, exp - 1, m)) % m;\n}\n```\n\nexp === 0 is met.\n\nTo avoid this situation, we provide for\nconditional statements , and allow return\nstatements to appear in the branches of the statement.\n\nUsing a\nconditional statement, we can write the function\nexpmod as follows:\n\n```javascript\neven_definition\n expmod_example\n\t4\n\nfunction expmod(base, exp, m) {\n if (exp === 0) {\n return 1;\n } else {\n if (is_even(exp)) {\n const half_exp = expmod(base, exp / 2, m);\n return (half_exp * half_exp) % m;\n } else {\n return (base * expmod(base, exp - 1, m)) % m;\n }\n }\n}\n```\n\nThe general form of a conditional statement is\n\n```javascript\nif (predicate) { consequent-statements } else { alternative-statements }\n```\n\nAs for a conditional expression, the interpreter first evaluates the\npredicate.\n\nIf it evaluates to true,\nthe interpreter evaluates the\nconsequent-statements in sequence, and if it\nevaluates to false, the interpreter evaluates\nalternative-statements in sequence.\n\nEvaluation of a return\nstatement returns from the surrounding function, ignoring any\nstatements in the sequence\n\nLet's use the substitution model to illustrate what happens:\n\n```javascript\nf(f)\nf(2)\n2(2)\n```\n\nThe application 2(2) leads to an error, since 2 is neither a primitive nor a compound function.", - "token_count": 253, + "content": "Conditional statements\nWe have seen that it is often useful to declare names that are local to\nfunction declarations.\n\nConsider for example expmod in\nexercise.\n\n```javascript\nexpmod_definition_2\n even_definition\n expmod_example_2\n\t4\n\nfunction expmod(base, exp, m) {\n return exp === 0\n ? 1\n : is_even(exp)\n ? ( expmod(base, exp / 2, m)\n * expmod(base, exp / 2, m)) % m\n : (base * expmod(base, exp - 1, m)) % m;\n}\n```\n\nThis function is unnecessarily inefficient, because it contains two identical calls:\n\n```javascript\nexpmod_example_2\n even_definition\n expmod_definition_2\n\nexpmod(base, exp / 2, m);\n\nexpmod(4, 3, 5);\n```\n\nWhile this can be easily fixed in this example using the\nsquare function, this is not so easy\nin general.\n\nWithout using square ,\nwe would be tempted to introduce a local name for the expression as\nfollows:\n\n```javascript\neven_definition\n expmod_example\n\nfunction expmod(base, exp, m) {\n const half_exp = expmod(base, exp / 2, m);\n return exp === 0\n ? 1\n : is_even(exp)\n ? (half_exp * half_exp) % m\n : (base * expmod(base, exp - 1, m)) % m;\n}\n```\n\nThis would make the function not just inefficient, but actually\nnonterminating!\n\nThe problem is that the constant declaration appears\noutside the conditional expression, which means that it is executed even\nwhen the base case exp === 0 is met.\n\nTo avoid this situation, we provide for\nconditional statements , and allow return\nstatements to appear in the branches of the statement.\n\nUsing a\nconditional statement, we can write the function\nexpmod as follows:\n\n```javascript\neven_definition\n expmod_example\n\t4\n\nfunction expmod(base, exp, m) {\n if (exp === 0) {\n return 1;\n } else {\n if (is_even(exp)) {\n const half_exp = expmod(base, exp / 2, m);\n return (half_exp * half_exp) % m;\n } else {\n return (base * expmod(base, exp - 1, m)) % m;\n }\n }\n}\n```\n\nThe general form of a conditional statement is\n\n```javascript\nif (predicate) { consequent-statements } else { alternative-statements }\n```", + "token_count": 319, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1220,8 +1220,18 @@ "chunk_id": "Building_Abstractions_with_Functions_Constructing_Functions_using_Lambda_Expressions_4" }, { - "content": "Consider the following three\nfunctions.\n\nThe first computes the sum of the integers from\n\n```javascript\nsum_integers_definition\n sum_integers_example\n 55\n\nfunction sum_integers(a, b) {\n return a > b\n ? 0\n : a + sum_integers(a + 1, b);\n}\n```\n\n```javascript\nsum_integers_example\n\nsum_integers(1, 10);\n```\n\nThe second computes the sum of the cubes of the integers in the given range:\n\n```javascript\nsum_cubes_definition\n cube_definition\n sum_cubes_example\n 775\n\nfunction sum_cubes(a, b) {\n return a > b\n ? 0\n : cube(a) + sum_cubes(a + 1, b);\n}\n```\n\n```javascript\nsum_cubes_example\n\nsum_cubes(3, 7);\n```\n\nThe third computes the sum of a sequence of terms in the series \\[ \\frac{1}{1\\cdot3}+\\frac{1}{5\\cdot7}+\\frac{1}{9\\cdot11}+\\cdots \\] which converges to $\\pi/8$ (very slowly):\n\n```javascript\npi_sum_definition\n pi_sum_example\n 3.139592655589783\n\nfunction pi_sum(a, b) {\n return a > b\n ? 0\n : 1 / (a * (a + 2)) + pi_sum(a + 4, b);\n}\n```\n\n```javascript\npi_sum_example\n\n8 * pi_sum(1, 1000);\n```\n\nThese three\nfunctions\nclearly share a common underlying pattern.\n\nThey are for the most part\nidentical, differing only in the name of the\nfunction,\nthe function of\nfunctions\nby filling in slots in the same template:\n\n```javascript\nfunction name(a, b) {\n return a > b\n ? 0\n : term(a) + name(next(a), b);\n}\n```\n\nThe presence of such a common pattern is strong evidence that there is a\nuseful\nsummation of a series and invented sigma\nnotation, for example\n\\[\\begin{array}{lll}\n\\displaystyle\\sum_{n=a}^{b}\\ f(n)&=&f(a)+\\cdots+f(b)\n\\end{array}\\]\nto express this concept.\n\nThe power of sigma notation is that it allows\nmathematicians to deal with the concept of summation itself rather than only\nwith particular sums for example, to formulate general results about\nsums that are independent of the particular series being summed.\n\nSimilarly, as program designers, we would like our language to be powerful\nenough so that we can write a\nfunction\nthat expresses the concept of summation itself rather than only\nfunctions\nthat compute particular sums.", - "token_count": 307, + "content": "The general form of a conditional statement is\n\nIf it evaluates to true,\nthe interpreter evaluates the\nconsequent-statements in sequence, and if it\nevaluates to false, the interpreter evaluates\nthe alternative-statements in sequence.\n\nEvaluation of a return\nstatement returns from the surrounding function, ignoring any\nstatements in the sequence\nafter the return statement and any statements after the conditional statement.\n\nNote that any constant declarations occurring in either part are local to that\npart, because each part is enclosed in braces and thus forms its own\nblock.\n\nLet's use the substitution model to illustrate what happens:\n\n```javascript\nf(f)\nf(2)\n2(2)\n```\n\nThe application 2(2) leads to an error, since 2 is neither a primitive nor a compound function.", + "token_count": 118, + "has_code": true, + "chapter": "Building Abstractions with Functions", + "section": "Formulating Abstractions with Higher-Order Functions", + "subsection": "Constructing Functions using Lambda Expressions", + "chunk_index": 5, + "chunk_id": "Building_Abstractions_with_Functions_Constructing_Functions_using_Lambda_Expressions_5" + }, + { + "content": "Consider the following three\nfunctions.\n\nThe first computes the sum of the integers from\nthrough :\n\n```javascript\nsum_integers_definition\n sum_integers_example\n 55\n\nfunction sum_integers(a, b) {\n return a > b\n ? 0\n : a + sum_integers(a + 1, b);\n}\n```\n\n```javascript\nsum_integers_example\n\nsum_integers(1, 10);\n```\n\nThe second computes the sum of the cubes of the integers in the given range:\n\n```javascript\nsum_cubes_definition\n cube_definition\n sum_cubes_example\n 775\n\nfunction sum_cubes(a, b) {\n return a > b\n ? 0\n : cube(a) + sum_cubes(a + 1, b);\n}\n```\n\n```javascript\nsum_cubes_example\n\nsum_cubes(3, 7);\n```\n\nThe third computes the sum of a sequence of terms in the series \\[ \\frac{1}{1\\cdot3}+\\frac{1}{5\\cdot7}+\\frac{1}{9\\cdot11}+\\cdots \\] which converges to $\\pi/8$ (very slowly):\n\n```javascript\npi_sum_definition\n pi_sum_example\n 3.139592655589783\n\nfunction pi_sum(a, b) {\n return a > b\n ? 0\n : 1 / (a * (a + 2)) + pi_sum(a + 4, b);\n}\n```\n\n```javascript\npi_sum_example\n\n8 * pi_sum(1, 1000);\n```\n\nThese three\nfunctions\nclearly share a common underlying pattern.\n\nThey are for the most part\nidentical, differing only in the name of the\nfunction,\nthe function of used to compute the term to\nbe added, and the function that provides the next value of.\n\nWe could generate each of the\nfunctions\nby filling in slots in the same template:\n\n```javascript\nfunction name(a, b) {\n return a > b\n ? 0\n : term(a) + name(next(a), b);\n}\n```\n\nThe presence of such a common pattern is strong evidence that there is a\nuseful\nabstraction waiting to be brought to the surface.\n\nIndeed,\nmathematicians long ago identified the abstraction of\nsummation of a series and invented sigma\nnotation, for example\n\\[\\begin{array}{lll}\n\\displaystyle\\sum_{n=a}^{b}\\ f(n)&=&f(a)+\\cdots+f(b)\n\\end{array}\\]\nto express this concept.\n\nThe power of sigma notation is that it allows\nmathematicians to deal with the concept of summation itself rather than only\nwith particular sums for example, to formulate general results about\nsums that are independent of the particular series being summed.", + "token_count": 313, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1230,8 +1240,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Arguments_1" }, { - "content": "Similarly, as program designers, we would like our language to be powerful\nenough so that we can write a\nfunction\nthat expresses the concept of summation itself rather than only\nfunctions\nthat compute particular sums.\n\nWe can do so readily in our\nfunctional\nlanguage by taking the common template shown above and transforming the\nslots into\nparameters:\n\n```javascript\nsum_definition\n\nfunction sum(term, a, next, b) {\n return a > b\n ? 0\n : term(a) + sum(term, next(a), next, b);\n}\n```\n\nNotice that\nfunctions\nfunction.\n\nFor example, we can use it (along with a\nfunction\nsum_cubes:\n\n```javascript\ninc_definition\n\nfunction inc(n) {\n return n + 1;\n}\n```\n\n```javascript\nsum_example\n cube_definition\n sum_definition\n sum_example_example\n 3025\n\nfunction inc(n) {\n return n + 1;\n}\nfunction sum_cubes(a, b) {\n return sum(cube, a, inc, b);\n}\n```\n\nUsing this, we can compute the sum of the cubes of the integers from 1 to 10:\n\n```javascript\nsum_example_example\n sum_example\n\nsum_cubes(1, 10);\n```\n\nWith the aid of an identity function to compute the term, we can define sum_@integers in terms of\n\n```javascript\nidentity\n identity_example\n\nfunction identity(x) {\n return x;\n}\n```\n\n```javascript\nidentity_example\n\nidentity(42);\n```\n\n```javascript\nsum_integers_definition2\n sum_definition\n inc_definition\n identity\n sum_integers_example2\n 55\n\nfunction sum_integers(a, b) {\n return sum(identity, a, inc, b);\n}\n```\n\nThen we can add up the integers from 1 to 10:\n\n```javascript\nsum_integers_example2\n sum_integers_definition2\n\nsum_integers(1, 10);\n```\n\nWe can also define pi_sum in the same way:\n\n```javascript\npi_sum_definition2\n sum_definition\n pi_sum_example2\n 3.139592655589783\n\nfunction pi_sum(a, b) {\n function pi_term(x) {\n return 1 / (x * (x + 2));\n }\n function pi_next(x) {\n return x + 4;\n }\n return sum(pi_term, a, pi_next, b);\n}\n```\n\nUsing these functions, we can compute an approximation to $\\pi$ :\n\n```javascript\npi_sum_example2\n pi_sum_definition2\n\n8 * pi_sum(1, 1000);\n```", - "token_count": 285, + "content": "The power of sigma notation is that it allows\nmathematicians to deal with the concept of summation itself rather than only\nwith particular sums for example, to formulate general results about\nsums that are independent of the particular series being summed.\n\nWe can do so readily in our\nfunctional\nlanguage by taking the common template shown above and transforming the\nslots into\nparameters:\n\n```javascript\nsum_definition\n\nfunction sum(term, a, next, b) {\n return a > b\n ? 0\n : term(a) + sum(term, next(a), next, b);\n}\n```\n\nNotice that takes as its arguments the\nlower and upper bounds and\ntogether with the\nfunctions\nand.\n\nWe can use just as we would any\nfunction.\n\nFor example, we can use it (along with a\nfunction\nthat increments its argument by 1) to define\nsum_cubes:\n\n```javascript\ninc_definition\n\nfunction inc(n) {\n return n + 1;\n}\n```\n\n```javascript\nsum_example\n cube_definition\n sum_definition\n sum_example_example\n 3025\n\nfunction inc(n) {\n return n + 1;\n}\nfunction sum_cubes(a, b) {\n return sum(cube, a, inc, b);\n}\n```\n\nUsing this, we can compute the sum of the cubes of the integers from 1 to 10:\n\n```javascript\nsum_example_example\n sum_example\n\nsum_cubes(1, 10);\n\n3025\n```\n\nWith the aid of an identity function to compute the term, we can define sum_@integers in terms of :\n\n```javascript\nidentity\n identity_example\n\nfunction identity(x) {\n return x;\n}\n```\n\n```javascript\nidentity_example\n\nidentity(42);\n```\n\n```javascript\nsum_integers_definition2\n sum_definition\n inc_definition\n identity\n sum_integers_example2\n 55\n\nfunction sum_integers(a, b) {\n return sum(identity, a, inc, b);\n}\n```\n\nThen we can add up the integers from 1 to 10:\n\n```javascript\nsum_integers_example2\n sum_integers_definition2\n\nsum_integers(1, 10);\n\n55\n```\n\nWe can also define pi_sum in the same way:\n\n```javascript\npi_sum_definition2\n sum_definition\n pi_sum_example2\n 3.139592655589783\n\nfunction pi_sum(a, b) {\n function pi_term(x) {\n return 1 / (x * (x + 2));\n }\n function pi_next(x) {\n return x + 4;\n }\n return sum(pi_term, a, pi_next, b);\n}\n```\n\nUsing these functions, we can compute an approximation to $\\pi$ :", + "token_count": 316, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1240,8 +1250,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Arguments_2" }, { - "content": "Using these functions, we can compute an approximation to $\\pi$ :\n\nOnce we have $f$ between the\nlimits $a$ and $b$ can\nbe approximated numerically using the formula\n\\[\n\\begin{array}{lll}\n\\displaystyle\\int_{a}^{b}f & = &\n\\left[\\,f\\!\\left( a+\\dfrac{dx}{2} \\right)\\,+\\,f\\!\\left(a+dx+\\dfrac{dx}{2}\n\\right)\\,+\\,f\\!\\left( a+2dx+\\dfrac{dx}{2}\\right)\\,+\\,\\cdots\n\\right] dx\n\\end{array}\n\\]\nfor small values of $dx$.\n\nWe can express this\ndirectly as a\nfunction:\n\n```javascript\nintegral_definition\n sum_definition\n integral_example\n 0.24998750000000042\n\nfunction integral(f, a, b, dx) {\n function add_dx(x) {\n return x + dx;\n }\n return sum(f, a + dx / 2, add_dx, b) * dx;\n}\n```\n\n```javascript\nintegral_example\n cube_definition\n integral_definition\n\nintegral(cube, 0, 1, 0.01);\n```\n\n```javascript\nintegral_example2\n cube_definition\n integral_definition\n\nintegral(cube, 0, 1, 0.001);\n\n// dare to try 0.001 with in browser?\nintegral(cube, 0, 1, 0.002);\n```\n\n(The exact value of the integral of", - "token_count": 125, + "content": "Using these functions, we can compute an approximation to $\\pi$ :\n\nOnce we have , we can use it as a building\nblock in formulating further concepts.\n\nFor instance, the\ndefinite integral of a function $f$ between the\nlimits $a$ and $b$ can\nbe approximated numerically using the formula\n\\[\n\\begin{array}{lll}\n\\displaystyle\\int_{a}^{b}f & = &\n\\left[\\,f\\!\\left( a+\\dfrac{dx}{2} \\right)\\,+\\,f\\!\\left(a+dx+\\dfrac{dx}{2}\n\\right)\\,+\\,f\\!\\left( a+2dx+\\dfrac{dx}{2}\\right)\\,+\\,\\cdots\n\\right] dx\n\\end{array}\n\\]\nfor small values of $dx$.\n\nWe can express this\ndirectly as a\nfunction:\n\n```javascript\nintegral_definition\n sum_definition\n integral_example\n 0.24998750000000042\n\nfunction integral(f, a, b, dx) {\n function add_dx(x) {\n return x + dx;\n }\n return sum(f, a + dx / 2, add_dx, b) * dx;\n}\n```\n\n```javascript\nintegral_example\n cube_definition\n integral_definition\n\nintegral(cube, 0, 1, 0.01);\n\n0.24998750000000042\n```\n\n```javascript\nintegral_example2\n cube_definition\n integral_definition\n\nintegral(cube, 0, 1, 0.001);\n\n// dare to try 0.001 with in browser?\nintegral(cube, 0, 1, 0.002);\n\n0.249999875000001\n```\n\n(The exact value of the integral of between 0 and 1 is 1/4.)", + "token_count": 154, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1260,8 +1270,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_General_Methods_1" }, { - "content": "Here is a\nfunction that implements this strategy:\n\n```javascript\npositive_negative_definition\n\nfunction positive(x) { return x > 0; }\nfunction negative(x) { return x < 0; }\n```\n\n```javascript\nsearch_definition\n average_definition\n positive_negative_definition\n close_enough_definition\n search_example\n 1\n\nfunction search(f, neg_point, pos_point) {\n const midpoint = average(neg_point, pos_point);\n if (close_enough(neg_point, pos_point)) {\n return midpoint;\n } else {\n const test_value = f(midpoint);\n return positive(test_value)\n ? search(f, neg_point, midpoint)\n : negative(test_value)\n ? search(f, midpoint, pos_point)\n : midpoint;\n }\n}\n```\n\n```javascript\nsearch_example\n\nsearch(x => x * x - 1, 0, 2);\n```\n\nWe assume that we are initially given the function\n$f$ together with points at which its values are\nnegative and positive.\n\nWe first compute the midpoint of the two given\npoints.\n\nNext we check to see if the given interval is small enough, and if\nso we simply return the midpoint as our answer.\n\nOtherwise, we compute as a\ntest value the value of $f$ at the midpoint.\n\nIf\nthe test value is positive, then we continue the process with a new interval\nrunning from the original negative point to the midpoint.\n\nIf the test value\nis negative, we continue with the interval from the midpoint to the positive\npoint.\n\nFinally, there is the possibility that the test value is 0, in\nwhich case the midpoint is itself the root we are searching for.\n\nTo test whether the endpoints are close enough we can use a\nfunction\nsimilar to the one used in section for\ncomputing square roots:\n\n```javascript\nclose_enough_definition\n abs_definition\n close_enough_example\n false\n\nfunction close_enough(x, y) {\n return abs(x - y) < 0.001;\n}\n```\n\n```javascript\nclose_enough_example\n\nclose_enough(7.7654, 7.7666);\n```\n\n```javascript\nThe function\n\tsearch\n```\n\nis awkward to use directly, because we can accidentally give it points at\nwhich $f$ s values do not have the required\nsign, in which case we get a wrong answer.", - "token_count": 302, + "content": "Here is a\nfunction that implements this strategy:\n\n```javascript\nsearch_definition\n average_definition\n positive_negative_definition\n close_enough_definition\n search_example\n 1\n\nfunction search(f, neg_point, pos_point) {\n const midpoint = average(neg_point, pos_point);\n if (close_enough(neg_point, pos_point)) {\n return midpoint;\n } else {\n const test_value = f(midpoint);\n return positive(test_value)\n ? search(f, neg_point, midpoint)\n : negative(test_value)\n ? search(f, midpoint, pos_point)\n : midpoint;\n }\n}\n```\n\n```javascript\nsearch_example\n\nsearch(x => x * x - 1, 0, 2);\n```\n\nWe assume that we are initially given the function\n$f$ together with points at which its values are\nnegative and positive.\n\nWe first compute the midpoint of the two given\npoints.\n\nNext we check to see if the given interval is small enough, and if\nso we simply return the midpoint as our answer.\n\nOtherwise, we compute as a\ntest value the value of $f$ at the midpoint.\n\nIf\nthe test value is positive, then we continue the process with a new interval\nrunning from the original negative point to the midpoint.\n\nIf the test value\nis negative, we continue with the interval from the midpoint to the positive\npoint.\n\nFinally, there is the possibility that the test value is 0, in\nwhich case the midpoint is itself the root we are searching for.\n\nTo test whether the endpoints are close enough we can use a\nfunction\nsimilar to the one used in section for\ncomputing square roots:\n\n```javascript\nclose_enough_definition\n abs_definition\n close_enough_example\n false\n\nfunction close_enough(x, y) {\n return abs(x - y) < 0.001;\n}\n```\n\n```javascript\nclose_enough_example\n\nclose_enough(7.7654, 7.7666);\n```\n\nThe function search\nis awkward to use directly, because we can accidentally give it points at\nwhich $f$ s values do not have the required\nsign, in which case we get a wrong answer.", + "token_count": 281, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1270,8 +1280,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_General_Methods_2" }, { - "content": "is awkward to use directly, because we can accidentally give it points at\nwhich $f$ s values do not have the required\nsign, in which case we get a wrong answer.\n\nInstead we will use\nfunction,\nwhich checks to see which of the endpoints has a negative function value and\nwhich has a positive value, and calls the\nfunction\naccordingly.\n\nIf the function has the same sign on the two given points, the\nhalf-interval method cannot be used, in which case the\nfunction\nsignals an error.\n\n```javascript\nhalf_definition\n search_definition\n half_example\n 3.14111328125\n\nfunction half_interval_method(f, a, b) {\n const a_value = f(a);\n const b_value = f(b);\n return negative(a_value) && positive(b_value)\n ? search(f, a, b)\n : negative(b_value) && positive(a_value)\n ? search(f, b, a)\n : error(\"values are not of opposite sign\");\n}\n```\n\nThe following example uses the $\\pi$ as the root between 2 and 4 of $\\sin\\, x = 0$ :\n\n```javascript\nhalf_example\n half_definition\n\nhalf_interval_method(math_sin, 2, 4);\n```\n\nHere is another example, using the half-interval method to search for a root of the equation $x^3 - 2x - 3 = 0$ between 1\n\nand 2:\n\n```javascript\nhalf_example2\n half_definition\n 1.89306640625\n\nhalf_interval_method(x => x * x * x - 2 * x - 3, 1, 2);\n```\n\nA number $x$ is called a\nfixed point of a\nfunction $f$ if $x$\nsatisfies the equation $f(x)=x$.\n\nFor some\nfunctions $f$ we can locate a fixed point by\nbeginning with an initial guess and applying $f$\nrepeatedly,\n\\[\n\\begin{array}{l}\nf(x), \\ f(f(x)), \\ f(f(f(x))), \\ \\ldots\n\\end{array}\n\\]\nuntil the value does not change very much.\n\nUsing this idea, we can devise a\nfunction\nfixed_point\nthat takes as inputs a function and an initial guess and produces an\napproximation to a fixed point of the function.", - "token_count": 289, + "content": "The function search\nis awkward to use directly, because we can accidentally give it points at\nwhich $f$ s values do not have the required\nsign, in which case we get a wrong answer.\n\nIf the function has the same sign on the two given points, the\nhalf-interval method cannot be used, in which case the\nfunction\nsignals an error.\n\n```javascript\nhalf_definition\n search_definition\n half_example\n 3.14111328125\n\nfunction half_interval_method(f, a, b) {\n const a_value = f(a);\n const b_value = f(b);\n return negative(a_value) && positive(b_value)\n ? search(f, a, b)\n : negative(b_value) && positive(a_value)\n ? search(f, b, a)\n : error(\"values are not of opposite sign\");\n}\n```\n\nThe following example uses the half-interval method to approximate $\\pi$ as the root between 2 and 4 of $\\sin\\, x = 0$ :\n\n```javascript\nhalf_example\n half_definition\n\nhalf_interval_method(math_sin, 2, 4);\n\n3.14111328125\n```\n\nHere is another example, using the half-interval method to search for a root of the equation $x^3 - 2x - 3 = 0$ between 1\n\nand 2:\n\n```javascript\nhalf_example2\n half_definition\n 1.89306640625\n\nhalf_interval_method(x => x * x * x - 2 * x - 3, 1, 2);\n\n1.89306640625\n```\n\nA number $x$ is called a\nfixed point of a\nfunction $f$ if $x$\nsatisfies the equation $f(x)=x$.\n\nFor some\nfunctions $f$ we can locate a fixed point by\nbeginning with an initial guess and applying $f$\nrepeatedly,\n\\[\n\\begin{array}{l}\nf(x), \\ f(f(x)), \\ f(f(f(x))), \\ \\ldots\n\\end{array}\n\\]\nuntil the value does not change very much.\n\nUsing this idea, we can devise a\nfunction\nfixed_point\nthat takes as inputs a function and an initial guess and produces an\napproximation to a fixed point of the function.\n\nWe apply the function\nrepeatedly until we find two successive values whose difference is less\nthan some prescribed tolerance:", + "token_count": 288, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1280,8 +1290,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_General_Methods_3" }, { - "content": "Using this idea, we can devise a\nfunction\nfixed_point\nthat takes as inputs a function and an initial guess and produces an\napproximation to a fixed point of the function.\n\nWe apply the function\nrepeatedly until we find two successive values whose difference is less\nthan some prescribed tolerance:\n\n```javascript\nfixed_definition\n abs_definition\n fixed_example\n 0.7390822985224023\n\nconst tolerance = 0.00001;\nfunction fixed_point(f, first_guess) {\n function close_enough(x, y) {\n return abs(x - y) < tolerance;\n }\n function try_with(guess) {\n const next = f(guess);\n return close_enough(guess, next)\n ? next\n : try_with(next);\n }\n return try_with(first_guess);\n}\n```\n\nFor example, we can use this method to approximate the fixed point of the\n\n```javascript\nfixed_example\n fixed_definition\n\nfixed_point(math_cos, 1);\n```\n\nSimilarly, we can find a solution to the equation $y=\\sin y + \\cos y$ :\n\n```javascript\nfixed_example2\n fixed_definition\n 1.2587315962971173\n\nfixed_point(y => math_sin(y) + math_cos(y), 1);\n```\n\nThe fixed-point process is reminiscent of the process we used for finding\nsquare roots in section.\n\nBoth are based on\nthe idea of repeatedly improving a guess until the result satisfies some\ncriterion.\n\nIn fact, we can readily formulate the\n$x$ requires finding a\n$y$ such that\n$y^2 = x$.\n\nPutting this equation into the\nequivalent form $y = x/y$ , we recognize that we\nare looking for a fixed point of the function $y \\mapsto x/y$ , and we can therefore try to\ncompute square roots as\n\n```javascript\nsqrt_definition2\n fixed_definition\n sqrt_example7\n\nfunction sqrt(x) {\n return fixed_point(y => x / y, 1);\n}\n```\n\n```javascript\nsqrt_example7\n\nsqrt(5);\n```\n\nUnfortunately, this fixed-point search does not converge.\n\nConsider an\ninitial guess $y_1$.\n\nThe next guess is\n$y_2 = x/y_1$ and the next guess is\n$y_3 = x/y_2 = x/(x/y_1) = y_1$.\n\nThis results\nin an infinite loop in which the two guesses\n$y_1$ and $y_2$ repeat\nover and over, oscillating about the answer.", - "token_count": 299, + "content": "We apply the function\nrepeatedly until we find two successive values whose difference is less\nthan some prescribed tolerance:\n\nFor example, we can use this method to approximate the fixed point of the cosine function, starting with 1 as an initial approximation:\n\n```javascript\nfixed_example\n fixed_definition\n\nfixed_point(math_cos, 1);\n\n0.7390822985224023\n```\n\nSimilarly, we can find a solution to the equation $y=\\sin y + \\cos y$ :\n\n```javascript\nfixed_example2\n fixed_definition\n 1.2587315962971173\n\nfixed_point(y => math_sin(y) + math_cos(y), 1);\n\n1.2587315962971173\n```\n\nThe fixed-point process is reminiscent of the process we used for finding\nsquare roots in section.\n\nBoth are based on\nthe idea of repeatedly improving a guess until the result satisfies some\ncriterion.\n\nIn fact, we can readily formulate the\nsquare-root computation as a fixed-point search.\n\nComputing the square root\nof some number $x$ requires finding a\n$y$ such that\n$y^2 = x$.\n\nPutting this equation into the\nequivalent form $y = x/y$ , we recognize that we\nare looking for a fixed point of the function\n$y \\mapsto x/y$ , and we can therefore try to\ncompute square roots as\n\n```javascript\nsqrt_definition2\n fixed_definition\n sqrt_example7\n\nfunction sqrt(x) {\n return fixed_point(y => x / y, 1);\n}\n```\n\n```javascript\nsqrt_example7\n\nsqrt(5);\n```\n\nUnfortunately, this fixed-point search does not converge.\n\nConsider an\ninitial guess $y_1$.\n\nThe next guess is\n$y_2 = x/y_1$ and the next guess is\n$y_3 = x/y_2 = x/(x/y_1) = y_1$.\n\nThis results\nin an infinite loop in which the two guesses\n$y_1$ and $y_2$ repeat\nover and over, oscillating about the answer.\n\nOne way to control such oscillations is to prevent the guesses from changing\nso much.", + "token_count": 265, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1290,8 +1300,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_General_Methods_4" }, { - "content": "This results\nin an infinite loop in which the two guesses\n$y_1$ and $y_2$ repeat\nover and over, oscillating about the answer.\n\nOne way to control such oscillations is to prevent the guesses from changing\nso much.\n\nSince the answer is always between our guess\n$y$\nand $x/y$ , we can make a new guess that is not as\nfar from $y$ as $x/y$\nby averaging $y$ with\n$x/y$ , so that the next guess after\n$y$ is\n$\\frac{1}{2}(y+x/y)$ instead of\n$x/y$.\n\nThe process of making such a sequence of\nguesses is simply the process of looking for a fixed point of\n$y \\mapsto \\frac{1}{2}(y+x/y)$ :\n\n```javascript\nsqrt_definition3\n fixed_definition\n average_definition\n sqrt_example7\n 2.236067977499978\n\nfunction sqrt(x) {\n return fixed_point(y => average(y, x / y), 1);\n}\n```\n\n(Note that $y=\\frac{1}{2}(y+x/y)$ is a simple transformation of the equation $y=x/y$ ; to derive it, add $y$ to both sides of the equation and divide\n\nby 2.)\n\nWith this modification, the square-root\nfunction\nworks.\n\nIn fact, if we unravel the definitions, we can see that the sequence\nof approximations to the square root generated here is precisely the same as\nthe one generated by our original square-root\nfunction\nof section.\n\nThis approach of averaging\nsuccessive approximations to a solution, a technique we call\naverage damping , often aids the convergence of fixed-point searches.", - "token_count": 218, + "content": "One way to control such oscillations is to prevent the guesses from changing\nso much.\n\nThe process of making such a sequence of\nguesses is simply the process of looking for a fixed point of\n$y \\mapsto \\frac{1}{2}(y+x/y)$ :\n\n```javascript\nsqrt_definition3\n fixed_definition\n average_definition\n sqrt_example7\n 2.236067977499978\n\nfunction sqrt(x) {\n return fixed_point(y => average(y, x / y), 1);\n}\n```\n\n(Note that $y=\\frac{1}{2}(y+x/y)$ is a simple transformation of the equation $y=x/y$ ; to derive it, add $y$ to both sides of the equation and divide\n\nby 2.)\n\nWith this modification, the square-root\nfunction\nworks.\n\nIn fact, if we unravel the definitions, we can see that the sequence\nof approximations to the square root generated here is precisely the same as\nthe one generated by our original square-root\nfunction\nof section.\n\nThis approach of averaging\nsuccessive approximations to a solution, a technique we call\naverage damping , often aids the convergence of fixed-point searches.", + "token_count": 151, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1300,8 +1310,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_General_Methods_5" }, { - "content": "The above examples demonstrate how the ability to pass\nfunctions\nas arguments significantly enhances the expressive power of our programming\nlanguage.\n\nWe can achieve even more expressive power by creating\nfunctions\nwhose returned values are themselves\nfunctions.\n\nWe can illustrate this idea by looking again at the fixed-point example\ndescribed at the end of\nsection.\n\nWe formulated a new\nversion of the square-root\nfunction\nas a fixed-point search, starting with the observation that\n$\\sqrt{x}$ is a fixed-point of the function\n$y\\mapsto x/y$.\n\nThen we used average damping to\nmake the approximations converge.\n\nAverage damping is a useful general\ntechnique in itself.\n\nNamely, given a\nfunction $f$ , we consider the function\nwhose value at $x$ is equal to the average of\n$x$ and $f(x)$.\n\nWe can express the idea of average damping by means of the following function:\n\n```javascript\naverage_damp_definition\n average_definition\n average_damp_example\n 55\n\nfunction average_damp(f) {\n return x => average(x, f(x));\n}\n```\n\n```javascript\nThe function\n average_damp\n```\n\ntakes as its argument a\nfunction\nfunction\n(produced by the lambda expression)\nthat, when applied to a number\nf(x).\n\nFor example, applying\naverage_damp\nto the\nfunction\nproduces a\nfunction\nwhose value at some number $x$ is the average of\n$x$ and $x^2$.\n\nApplying this resulting\nfunction\nto 10 returns the average of 10 and 100, or 55:\n\n```javascript\naverage_damp_example\n average_damp_definition\n square_definition\n\naverage_damp(square)(10);\n```\n\nUsing average_damp, we can reformulate the function as follows:\n\n```javascript\nsqrt_definition4\n average_damp_definition\n fixed_definition\n sqrt_example3\n\nfunction sqrt(x) {\n return fixed_point(average_damp(y => x / y), 1);\n}\n```\n\n```javascript\nsqrt_example3\n sqrt_definition4\n 2.4494897427875517\n\nsqrt(6);\n```\n\nNotice how this formulation makes explicit the three ideas in the method:\nfixed-point search, average damping, and the function\n$y\\mapsto x/y$.\n\nIt is instructive to compare\nthis formulation of the square-root method with the original version given\nin section.", - "token_count": 292, + "content": "The above examples demonstrate how the ability to pass\nfunctions\nas arguments significantly enhances the expressive power of our programming\nlanguage.\n\nWe can achieve even more expressive power by creating\nfunctions\nwhose returned values are themselves\nfunctions.\n\nWe can illustrate this idea by looking again at the fixed-point example\ndescribed at the end of\nsection.\n\nWe formulated a new\nversion of the square-root\nfunction\nas a fixed-point search, starting with the observation that\n$\\sqrt{x}$ is a fixed-point of the function\n$y\\mapsto x/y$.\n\nThen we used average damping to\nmake the approximations converge.\n\nAverage damping is a useful general\ntechnique in itself.\n\nNamely, given a\nfunction $f$ , we consider the function\nwhose value at $x$ is equal to the average of\n$x$ and $f(x)$.\n\nWe can express the idea of average damping by means of the following function:\n\n```javascript\naverage_damp_definition\n average_definition\n average_damp_example\n 55\n\nfunction average_damp(f) {\n return x => average(x, f(x));\n}\n```\n\nThe function average_damp\ntakes as its argument a\nfunction\nand returns as its value a\nfunction\n(produced by the lambda expression)\nthat, when applied to a number , produces the\naverage of and\nf(x).\n\nFor example, applying\naverage_damp\nto the\nfunction\nproduces a\nfunction\nwhose value at some number $x$ is the average of\n$x$ and $x^2$.\n\nApplying this resulting\nfunction\nto 10 returns the average of 10 and 100, or 55:\n\n```javascript\naverage_damp_example\n average_damp_definition\n square_definition\n\naverage_damp(square)(10);\n\n55\n```\n\nUsing average_damp, we can reformulate the square-root function as follows:\n\n```javascript\nsqrt_definition4\n average_damp_definition\n fixed_definition\n sqrt_example3\n\nfunction sqrt(x) {\n return fixed_point(average_damp(y => x / y), 1);\n}\n```\n\n```javascript\nsqrt_example3\n sqrt_definition4\n 2.4494897427875517\n\nsqrt(6);\n```\n\nNotice how this formulation makes explicit the three ideas in the method:\nfixed-point search, average damping, and the function\n$y\\mapsto x/y$.\n\nIt is instructive to compare\nthis formulation of the square-root method with the original version given\nin section.", + "token_count": 304, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1310,8 +1320,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Returned_Values_1" }, { - "content": "It is instructive to compare\nthis formulation of the square-root method with the original version given\nin section.\n\nBear in mind that these\nfunctions\nexpress the same process, and notice how much clearer the idea becomes when\nwe express the process in terms of these abstractions.\n\nIn general, there\nare many ways to formulate a process as a\nfunction.\n\nExperienced programmers know how to choose\nprocess\nformulations that are particularly perspicuous, and where useful elements of\nthe process are exposed as separate entities that can be reused in other\napplications.\n\nAs a simple example of reuse, notice that the cube root of\n$x$ is a fixed point of the function\n$y\\mapsto x/y^2$ , so we can immediately\ngeneralize our square-root\nfunction\nto one that extracts\n\n```javascript\ncube_root_definition\n average_damp_definition\n fixed_definition\n square_definition\n cube_root_example\n\nfunction cube_root(x) {\n return fixed_point(average_damp(y => x / square(y)), 1);\n}\n```\n\n```javascript\ncube_root_example\n cube_root_definition\n 2.9999972321057697\n\ncube_root(27);\n```\n\nWhen we first introduced the square-root\nfunction,\nin section , we mentioned that this was a\nspecial case of\nNewton s method.\n\nIf\n$x\\mapsto g(x)$ is a differentiable function,\nthen a solution of the equation $g(x)=0$ is a\nfixed point of the function $x\\mapsto f(x)$ where\n\\[\n\\begin{array}{lll}\nf(x) & = & x - \\dfrac{g(x)}{Dg(x)}\n\\end{array}\n\\]\nand $Dg(x)$ is the derivative of\n$g$ evaluated at $x$.\ns method is the use of the fixed-point method we saw above to\napproximate a solution of the equation by finding a fixed point of the\nfunction $f$. $g$ and for sufficiently good\ninitial guesses for $x$ , Newton s method\nconverges very rapidly to a solution of\n$g(x)=0$.\n\nIn order to implement Newton s method as a\nfunction,\nwe must first express the idea of\nderivative, like average damping, is something that\ntransforms a function into another function.", - "token_count": 295, + "content": "It is instructive to compare\nthis formulation of the square-root method with the original version given\nin section.\n\nIn general, there\nare many ways to formulate a process as a\nfunction.\n\nExperienced programmers know how to choose\nprocess\nformulations that are particularly perspicuous, and where useful elements of\nthe process are exposed as separate entities that can be reused in other\napplications.\n\nAs a simple example of reuse, notice that the cube root of\n$x$ is a fixed point of the function\n$y\\mapsto x/y^2$ , so we can immediately\ngeneralize our square-root\nfunction\nto one that extracts\ncube roots:\n\n```javascript\ncube_root_definition\n average_damp_definition\n fixed_definition\n square_definition\n cube_root_example\n\nfunction cube_root(x) {\n return fixed_point(average_damp(y => x / square(y)), 1);\n}\n```\n\n```javascript\ncube_root_example\n cube_root_definition\n 2.9999972321057697\n\ncube_root(27);\n```\n\nWhen we first introduced the square-root\nfunction,\nin section , we mentioned that this was a\nspecial case of\nNewton s method.\n\nIf\n$x\\mapsto g(x)$ is a differentiable function,\nthen a solution of the equation $g(x)=0$ is a\nfixed point of the function $x\\mapsto f(x)$ where\n\\[\n\\begin{array}{lll}\nf(x) & = & x - \\dfrac{g(x)}{Dg(x)}\n\\end{array}\n\\]\nand $Dg(x)$ is the derivative of\n$g$ evaluated at $x$.\n\nNewton s method is the use of the fixed-point method we saw above to\napproximate a solution of the equation by finding a fixed point of the\nfunction $f$.\n\nFor many functions $g$ and for sufficiently good\ninitial guesses for $x$ , Newton s method\nconverges very rapidly to a solution of\n$g(x)=0$.\n\nIn order to implement Newton s method as a\nfunction,\nwe must first express the idea of\nderivative.\n\nNote that\nderivative, like average damping, is something that\ntransforms a function into another function.\n\nFor instance, the derivative\nof the function $x\\mapsto x^3$ is the function\n$x \\mapsto 3x^2$.", + "token_count": 291, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1320,8 +1330,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Returned_Values_2" }, { - "content": "In order to implement Newton s method as a\nfunction,\nwe must first express the idea of\nderivative, like average damping, is something that\ntransforms a function into another function.\n\nFor instance, the derivative\nof the function $x\\mapsto x^3$ is the function\n$x \\mapsto 3x^2$.\n\nIn general, if\n$g$ is a function and\n$dx$ is a small number, then the derivative\n$Dg$ of $g$ is the\nfunction whose value at any number $x$ is given\n(in the limit of small $dx$ ) by\n\\[\n\\begin{array}{lll}\nDg(x) & = & \\dfrac {g(x+dx) - g(x)}{dx}\n\\end{array}\n\\]\nThus, we can express the idea of derivative (taking\n$dx$ to be, say, 0.00001) as the\nfunction\n\n```javascript\nderiv_definition\n dx\n deriv_example\n\nfunction deriv(g) {\n return x => (g(x + dx) - g(x)) / dx;\n}\n```\n\nalong with the declaration\n\n```javascript\ndx\n\nconst dx = 0.00001;\n```\n\nLike\naverage_damp,\nfunction\nthat takes a\nfunction\nas argument and returns a\nfunction\nas value.\n\nFor example, to approximate the derivative of\n$x \\mapsto x^3$ at 5 (whose exact value is 75)\nwe can evaluate\n\n```javascript\nderiv_example\n deriv_definition\n 75.00014999664018\n\nfunction cube(x) { return x * x * x; }\n\nderiv(cube)(5);\n```\n\nWith the aid of s method as a fixed-point process:\n\n```javascript\nnewtons_method_definition\n fixed_definition\n deriv_definition\n sqrt_example4\n 2.4494897427970397\n\nfunction newton_transform(g) {\n return x => x - g(x) / deriv(g)(x);\n}\nfunction newtons_method(g, guess) {\n return fixed_point(newton_transform(g), guess);\n}\n```\n\nThe\nnewton_transform\nfunction\nexpresses the formula at the beginning of this section, and\nnewtons_method\nis readily defined in terms of this.\n\nIt takes as arguments a\nfunction\nthat computes the function for which we want to find a zero, together with\nan initial guess.\n\nFor instance, to find the\nsquare root of $x$ , we can use\ns\nmethod to find a zero of the function\n$y\\mapsto y^2-x$ starting with an initial guess\nof 1.\nfunction:", - "token_count": 306, + "content": "For instance, the derivative\nof the function $x\\mapsto x^3$ is the function\n$x \\mapsto 3x^2$.\n\n```javascript\nderiv_definition\n dx\n deriv_example\n\nfunction deriv(g) {\n return x => (g(x + dx) - g(x)) / dx;\n}\n```\n\nalong with the declaration\n\n```javascript\ndx\n\nconst dx = 0.00001;\n```\n\nLike\naverage_damp,\nis a\nfunction\nthat takes a\nfunction\nas argument and returns a\nfunction\nas value.\n\nFor example, to approximate the derivative of\n$x \\mapsto x^3$ at 5 (whose exact value is 75)\nwe can evaluate\n\n```javascript\nderiv_example\n deriv_definition\n 75.00014999664018\n\nfunction cube(x) { return x * x * x; }\n\nderiv(cube)(5);\n\n75.00014999664018\n```\n\nWith the aid of , we can express Newton s method as a fixed-point process:\n\n```javascript\nnewtons_method_definition\n fixed_definition\n deriv_definition\n sqrt_example4\n 2.4494897427970397\n\nfunction newton_transform(g) {\n return x => x - g(x) / deriv(g)(x);\n}\nfunction newtons_method(g, guess) {\n return fixed_point(newton_transform(g), guess);\n}\n```\n\nThe\nnewton_transform\nfunction\nexpresses the formula at the beginning of this section, and\nnewtons_method\nis readily defined in terms of this.\n\nIt takes as arguments a\nfunction\nthat computes the function for which we want to find a zero, together with\nan initial guess.\n\nFor instance, to find the\nsquare root of $x$ , we can use\nNewton s\nmethod to find a zero of the function\n$y\\mapsto y^2-x$ starting with an initial guess\nof 1.\n\nThis provides yet another form of the square-root\nfunction:\n\n```javascript\nsqrt_definition5\n newtons_method_definition\n square_definition\n sqrt_example4\n\nfunction sqrt(x) {\n return newtons_method(y => square(y) - x, 1);\n}\n```\n\n```javascript\nsqrt_example4\n sqrt_definition5\n\nsqrt(6);\n```\n\nWe ve seen two ways to express the square-root computation as an\ninstance of a more general method, once as a fixed-point search and once\nusing Newton s method.\n\nSince Newton s method was itself\nexpressed as a fixed-point process, we actually saw two ways to compute\nsquare roots as fixed points.", + "token_count": 300, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1330,8 +1340,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Returned_Values_3" }, { - "content": "For instance, to find the\nsquare root of $x$ , we can use\ns\nmethod to find a zero of the function\n$y\\mapsto y^2-x$ starting with an initial guess\nof 1.\nfunction:\n\n```javascript\nsqrt_definition5\n newtons_method_definition\n square_definition\n sqrt_example4\n\nfunction sqrt(x) {\n return newtons_method(y => square(y) - x, 1);\n}\n```\n\n```javascript\nsqrt_example4\n sqrt_definition5\n\nsqrt(6);\n```\n\nWe ve seen two ways to express the square-root computation as an\ninstance of a more general method, once as a fixed-point search and once\nusing Newton s method.\n\nSince Newton s method was itself\nexpressed as a fixed-point process, we actually saw two ways to compute\nsquare roots as fixed points.\n\nEach method begins with a function and finds a\nfunction:\n\n```javascript\nfixed_point_of_transform_definition\n fixed_definition\n sqrt_example5\n 2.4494897427875517\n\nfunction fixed_point_of_transform(g, transform, guess) {\n return fixed_point(transform(g), guess);\n}\n```\n\nThis very general function takes as its arguments a function function that transforms\n\nUsing this abstraction, we can recast the first square-root computation $y \\mapsto x/y$ ) as an instance of this general method:\n\n```javascript\nsqrt_definition6\n fixed_point_of_transform_definition\n average_damp_definition\n sqrt_example5\n 2.4494897427875517\n\nfunction sqrt(x) {\n return fixed_point_of_transform(\n y => x / y,\n average_damp,\n 1);\n}\n```\n\n```javascript\nsqrt_example5\n sqrt_definition6\n\nsqrt(6);\n```\n\nSimilarly, we can express the second square-root computation from this section (an instance of s method that finds a fixed point of the Newton transform\n\nof $y\\mapsto y^2-x$ ) as\n\n```javascript\nsqrt_definition7\n fixed_point_of_transform_definition\n square_definition\n newtons_method_definition\n sqrt_example6\n\nfunction sqrt(x) {\n return fixed_point_of_transform(\n y => square(y) - x,\n newton_transform,\n 1);\n}\n```\n\n```javascript\nsqrt_example6\n sqrt_definition7\n\nsqrt(6);\n```\n\nWe began section with the\nobservation that compound\nfunctions\nare a crucial abstraction mechanism, because they permit us to express\ngeneral methods of computing as explicit elements in our programming\nlanguage.\n\nNow we ve seen how higher-order\nfunctions\npermit us to manipulate these general methods to create further abstractions.", - "token_count": 294, + "content": "Since Newton s method was itself\nexpressed as a fixed-point process, we actually saw two ways to compute\nsquare roots as fixed points.\n\nWe can express this\ngeneral idea itself as a\nfunction:\n\n```javascript\nfixed_point_of_transform_definition\n fixed_definition\n sqrt_example5\n 2.4494897427875517\n\nfunction fixed_point_of_transform(g, transform, guess) {\n return fixed_point(transform(g), guess);\n}\n```\n\nThis very general\nfunction\ntakes as its arguments a\nfunction\nthat computes some function, a\nfunction\nthat transforms , and an initial guess.\n\nThe returned result is a fixed point of the transformed function.\n\nUsing this abstraction, we can recast the first square-root computation from this section (where we look for a fixed point of the average-damped version of\n\n$y \\mapsto x/y$ ) as an instance of this general method:\n\n```javascript\nsqrt_definition6\n fixed_point_of_transform_definition\n average_damp_definition\n sqrt_example5\n 2.4494897427875517\n\nfunction sqrt(x) {\n return fixed_point_of_transform(\n y => x / y,\n average_damp,\n 1);\n}\n```\n\n```javascript\nsqrt_example5\n sqrt_definition6\n\nsqrt(6);\n```\n\nSimilarly, we can express the second square-root computation from this section (an instance of Newton s method that finds a fixed point of the Newton\n\ntransform of $y\\mapsto y^2-x$ ) as\n\n```javascript\nsqrt_definition7\n fixed_point_of_transform_definition\n square_definition\n newtons_method_definition\n sqrt_example6\n\nfunction sqrt(x) {\n return fixed_point_of_transform(\n y => square(y) - x,\n newton_transform,\n 1);\n}\n```\n\n```javascript\nsqrt_example6\n sqrt_definition7\n\nsqrt(6);\n```\n\nWe began section with the\nobservation that compound\nfunctions\nare a crucial abstraction mechanism, because they permit us to express\ngeneral methods of computing as explicit elements in our programming\nlanguage.\n\nNow we ve seen how higher-order\nfunctions\npermit us to manipulate these general methods to create further abstractions.\n\nAs programmers, we should be alert to opportunities to identify the\nunderlying abstractions in our programs and to build upon them and\ngeneralize them to create more powerful abstractions.\n\nThis is not to say\nthat one should always write programs in the most abstract way possible;\nexpert programmers know how to choose the level of abstraction appropriate\nto their task.", + "token_count": 308, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1340,8 +1350,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Returned_Values_4" }, { - "content": "Now we ve seen how higher-order\nfunctions\npermit us to manipulate these general methods to create further abstractions.\n\nAs programmers, we should be alert to opportunities to identify the\nunderlying abstractions in our programs and to build upon them and\ngeneralize them to create more powerful abstractions.\n\nThis is not to say\nthat one should always write programs in the most abstract way possible;\nexpert programmers know how to choose the level of abstraction appropriate\nto their task.\n\nBut it is important to be able to think in terms of these\nabstractions, so that we can be ready to apply them in new contexts.\n\nThe\nsignificance of higher-order\nfunctions\nis that they enable us to represent these abstractions explicitly as\nelements in our programming language, so that they can be handled just\nlike other computational elements.\n\nIn general, programming languages impose restrictions on the ways in which\ncomputational elements can be manipulated.\n\nElements with the fewest\nrestrictions are said to have\nfirst-class status.\n\nSome of the rights and\nprivileges of first-class elements are:\n-\n-\nThey may be referred to using names.\n-\n-\nThey may be passed as arguments to\nfunctions.\n-\n-\nThey may be returned as the results of\nfunctions.\n-\n-\nThey may be included in data structures.\n\nJavaScript,\nlike other high-level\nprogramming languages, awards\nfunctions\nfull first-class status.\n\nThis poses challenges for efficient\nimplementation, but the resulting gain in expressive power is\nenormous.", - "token_count": 238, + "content": "This is not to say\nthat one should always write programs in the most abstract way possible;\nexpert programmers know how to choose the level of abstraction appropriate\nto their task.\n\nThe\nsignificance of higher-order\nfunctions\nis that they enable us to represent these abstractions explicitly as\nelements in our programming language, so that they can be handled just\nlike other computational elements.\n\nIn general, programming languages impose restrictions on the ways in which\ncomputational elements can be manipulated.\n\nElements with the fewest\nrestrictions are said to have\nfirst-class status.\n\nSome of the rights and\nprivileges of first-class elements are:\n-\n-\nThey may be referred to using names.\n-\n-\nThey may be passed as arguments to\nfunctions.\n-\n-\nThey may be returned as the results of\nfunctions.\n-\n-\nThey may be included in data structures.\n\nJavaScript,\nlike other high-level\nprogramming languages, awards\nfunctions\nfull first-class status.\n\nThis poses challenges for efficient\nimplementation, but the resulting gain in expressive power is\nenormous.", + "token_count": 165, "has_code": false, "chapter": "Building Abstractions with Functions", "section": "Formulating Abstractions with Higher-Order Functions", @@ -1350,8 +1360,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Returned_Values_5" }, { - "content": "We have now considered the elements of programming: We have used\nprimitive arithmetic operations, we have combined these operations, and\nwe have abstracted these composite operations by\ndeclaring them as compound functions.\n\nBut that is not enough to enable us to say that we know\nhow to program.\n\nOur situation is analogous to that of someone who has\nlearned the rules for how the pieces move in chess but knows nothing\nof typical openings, tactics, or strategy.\n\nLike the novice chess\nplayer, we don t yet know the common patterns of usage in the domain.\n\nWe lack the knowledge of which moves are worth making\n(which functions are worth declaring).\n\nWe lack the experience to predict the consequences of making a move\n(executing a function).\n\nThe ability to visualize the consequences of the actions under\nconsideration is crucial to becoming an expert programmer, just as it\nis in any synthetic, creative activity.\n\nIn becoming an expert\nphotographer, for example, one must learn how to look at a scene and\nknow how dark each region will appear on a print for each possible\nchoice of exposure and\nprocessing options.\nprocessing\nto obtain the desired effects.\n\nSo it is with programming, where we are\nplanning the course of action to be taken by a process and where we control\nthe process by means of a program.\n\nTo become experts, we must learn to\nvisualize the processes generated by various types of\nfunctions.\n\nOnly after we have developed such a skill can we learn\nto reliably construct programs that exhibit the desired behavior.\n\nA\nfunction\nis a\nlocal evolution of a\ncomputational process.\n\nIt specifies how each stage of the process is\nbuilt upon the previous stage.", - "token_count": 284, + "content": "We have now considered the elements of programming: We have used\nprimitive arithmetic operations, we have combined these operations, and\nwe have abstracted these composite operations by\ndeclaring them as compound functions.\n\nBut that is not enough to enable us to say that we know\nhow to program.\n\nOur situation is analogous to that of someone who has\nlearned the rules for how the pieces move in chess but knows nothing\nof typical openings, tactics, or strategy.\n\nLike the novice chess\nplayer, we don t yet know the common patterns of usage in the domain.\n\nWe lack the knowledge of which moves are worth making\n(which functions are worth declaring).\n\nWe lack the experience to predict the consequences of making a move\n(executing a function).\n\nThe ability to visualize the consequences of the actions under\nconsideration is crucial to becoming an expert programmer, just as it\nis in any synthetic, creative activity.\n\nIn becoming an expert\nphotographer, for example, one must learn how to look at a scene and\nknow how dark each region will appear on a print for each possible\nchoice of exposure and\nprocessing options.\n\nOnly then can one reason backward, planning framing, lighting,\nexposure, and\nprocessing\nto obtain the desired effects.\n\nSo it is with programming, where we are\nplanning the course of action to be taken by a process and where we control\nthe process by means of a program.\n\nTo become experts, we must learn to\nvisualize the processes generated by various types of\nfunctions.\n\nOnly after we have developed such a skill can we learn\nto reliably construct programs that exhibit the desired behavior.\n\nA\nfunction\nis a\npattern for the local evolution of a\ncomputational process.\n\nIt specifies how each stage of the process is\nbuilt upon the previous stage.", + "token_count": 298, "has_code": false, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1360,8 +1370,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_and_the_Processes_They_Generate_1" }, { - "content": "It specifies how each stage of the process is\nbuilt upon the previous stage.\n\nWe would like to be able to make\nstatements about the overall, or global , behavior of a\nprocess whose local\nfunction.\n\nThis is very difficult to do in general, but we can at least try to\ndescribe some typical patterns of process evolution.\n\nIn this section we will examine some common shapes for\nprocesses generated by simple\nfunctions.\n\nWe will also investigate the rates at which these processes consume the\nimportant computational resources of time and space.\n\nThe\nfunctions\nwe will consider are very simple.\n\nTheir role is like that played by test\npatterns in photography: as oversimplified prototypical patterns, rather\nthan practical examples in their own right.", - "token_count": 123, + "content": "It specifies how each stage of the process is\nbuilt upon the previous stage.\n\nThis is very difficult to do in general, but we can at least try to\ndescribe some typical patterns of process evolution.\n\nIn this section we will examine some common shapes for\nprocesses generated by simple\nfunctions.\n\nWe will also investigate the rates at which these processes consume the\nimportant computational resources of time and space.\n\nThe\nfunctions\nwe will consider are very simple.\n\nTheir role is like that played by test\npatterns in photography: as oversimplified prototypical patterns, rather\nthan practical examples in their own right.", + "token_count": 101, "has_code": false, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1370,8 +1380,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_and_the_Processes_They_Generate_2" }, { - "content": "This section describes two methods for checking the primality of an\ninteger $n$ , one with order of growth\n$\\Theta(\\sqrt{n})$ , and a\nprobabilistic algorithm with order of growth\n$\\Theta(\\log n)$.\n\nThe exercises at the end of\nthis section suggest programming projects based on these algorithms.\n\nSince ancient times, mathematicians have been fascinated by problems\nconcerning prime numbers, and many people have worked on the problem\nof determining ways to test if numbers are prime.\n\nOne way\nto test if a number is prime is to find the number s divisors.\n\nThe\nfollowing program finds the smallest integral divisor (greater than 1)\nof a given number $n$.\n\nIt does this in a\nstraightforward way, by testing $n$ for\ndivisibility by successive integers starting with 2.\n\n```javascript\nsmallest_divisor_definition\n square_definition\n smallest_divisor_example\n\nfunction smallest_divisor(n) {\n return find_divisor(n, 2);\n}\nfunction find_divisor(n, test_divisor) {\n return square(test_divisor) > n\n ? n\n : divides(test_divisor, n)\n ? test_divisor\n : find_divisor(n, test_divisor + 1);\n}\nfunction divides(a, b) {\n return b % a === 0;\n}\n```\n\n```javascript\nsmallest_divisor_example\n smallest_divisor_definition\n 2\n\nsmallest_divisor(42);\n```\n\nWe can test whether a number is prime as follows: $n$ is prime if and only if $n$ is its own smallest divisor.\n\n```javascript\nprime_definition\n smallest_divisor_definition\n prime_example\n\nfunction is_prime(n) {\n return n === smallest_divisor(n);\n}\n```\n\n```javascript\nprime_example\n prime_definition\n false\n\nis_prime(42);\n```\n\nThe end test for\nfind_divisor\nis based on the fact that if $n$ is not prime it\nmust have a divisor less than or equal to\n$\\sqrt{n}$. $\\sqrt{n}$.\n\nConsequently, the number of steps\nrequired to identify $n$ as prime will have order\nof growth $\\Theta(\\sqrt{n})$.\n\nThe $\\Theta(\\log n)$ primality test is based on\na result from number theory known as\ns Little\nTheorem.", - "token_count": 280, + "content": "This section describes two methods for checking the primality of an\ninteger $n$ , one with order of growth\n$\\Theta(\\sqrt{n})$ , and a\nprobabilistic algorithm with order of growth\n$\\Theta(\\log n)$.\n\nThe exercises at the end of\nthis section suggest programming projects based on these algorithms.\n\nSince ancient times, mathematicians have been fascinated by problems\nconcerning prime numbers, and many people have worked on the problem\nof determining ways to test if numbers are prime.\n\nOne way\nto test if a number is prime is to find the number s divisors.\n\nThe\nfollowing program finds the smallest integral divisor (greater than 1)\nof a given number $n$.\n\nIt does this in a\nstraightforward way, by testing $n$ for\ndivisibility by successive integers starting with 2.\n\n```javascript\nsmallest_divisor_definition\n square_definition\n smallest_divisor_example\n\nfunction smallest_divisor(n) {\n return find_divisor(n, 2);\n}\nfunction find_divisor(n, test_divisor) {\n return square(test_divisor) > n\n ? n\n : divides(test_divisor, n)\n ? test_divisor\n : find_divisor(n, test_divisor + 1);\n}\nfunction divides(a, b) {\n return b % a === 0;\n}\n```\n\n```javascript\nsmallest_divisor_example\n smallest_divisor_definition\n 2\n\nsmallest_divisor(42);\n```\n\nWe can test whether a number is prime as follows: $n$ is prime if and only if $n$ is its own smallest divisor.\n\n```javascript\nprime_definition\n smallest_divisor_definition\n prime_example\n\nfunction is_prime(n) {\n return n === smallest_divisor(n);\n}\n```\n\n```javascript\nprime_example\n prime_definition\n false\n\nis_prime(42);\n```\n\nThe end test for\nfind_divisor\nis based on the fact that if $n$ is not prime it\nmust have a divisor less than or equal to\n$\\sqrt{n}$.\n\nThis means that the algorithm need only test divisors between 1 and\n$\\sqrt{n}$.\n\nConsequently, the number of steps\nrequired to identify $n$ as prime will have order\nof growth $\\Theta(\\sqrt{n})$.\n\nThe $\\Theta(\\log n)$ primality test is based on\na result from number theory known as\nFermat s Little\nTheorem.", + "token_count": 293, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1380,8 +1390,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Example_Testing_for_Primality_1" }, { - "content": "The $\\Theta(\\log n)$ primality test is based on\na result from number theory known as\ns Little\nTheorem.\n\nFermat s Little Theorem:\n$n$ is a prime number and\n$a$ is any positive integer less than\n$n$ , then $a$ raised\nto the $n$ th power is congruent to\n$a$ modulo $n$.\n\n(Two numbers are said to be\ncongruent modulo\n$n$ if they both have the same remainder when\ndivided by $n$.\n\nThe remainder of a number\n$a$ when divided by\n$n$ is also referred to as the\nremainder of $a$ modulo\n$n$ , or simply as $a$\nmodulo $n$.)\n\nIf $n$ is not prime, then, in general, most of\nthe numbers $a < n$ will not satisfy the above\nrelation.\n\nThis leads to the following algorithm for testing primality:\nGiven a number $n$ , pick a\n$a < n$ and compute the\nremainder of $a^n$ modulo\n$n$.\n\nIf the result is not equal to\n$a$ , then $n$ is\ncertainly not prime.\n\nIf it is $a$ , then chances\nare good that $n$ is prime.\n\nNow pick another\nrandom number $a$ and test it with the same\nmethod.\n\nIf it also satisfies the equation, then we can be even more\nconfident that $n$ is prime.\n\nBy trying more and\nmore values of $a$ , we can increase our\nconfidence in the result.\n\nThis algorithm is known as the Fermat test.\n\nTo implement the Fermat test, we need a function that computes the\n\n```javascript\nexpmod_definition\n expmod_example\n even_definition\n square_definition\n\nfunction expmod(base, exp, m) {\n return exp === 0\n ? 1\n : is_even(exp)\n ? square(expmod(base, exp / 2, m)) % m\n : (base * expmod(base, exp - 1, m)) % m;\n}\n```\n\n```javascript\nexpmod_example\n expmod_definition\n 4\n\nexpmod(4, 3, 5);\n```\n\nThis is very similar to the\nfast_expt\nfunction\nof section.", - "token_count": 298, + "content": "The $\\Theta(\\log n)$ primality test is based on\na result from number theory known as\nFermat s Little\nTheorem.\n\n(Two numbers are said to be\ncongruent modulo\n$n$ if they both have the same remainder when\ndivided by $n$.\n\nThe remainder of a number\n$a$ when divided by\n$n$ is also referred to as the\nremainder of $a$ modulo\n$n$ , or simply as $a$\nmodulo $n$.)\n\nIf $n$ is not prime, then, in general, most of\nthe numbers $a < n$ will not satisfy the above\nrelation.\n\nThis leads to the following algorithm for testing primality:\nGiven a number $n$ , pick a\nrandom number $a < n$ and compute the\nremainder of $a^n$ modulo\n$n$.\n\nIf the result is not equal to\n$a$ , then $n$ is\ncertainly not prime.\n\nIf it is $a$ , then chances\nare good that $n$ is prime.\n\nNow pick another\nrandom number $a$ and test it with the same\nmethod.\n\nIf it also satisfies the equation, then we can be even more\nconfident that $n$ is prime.\n\nBy trying more and\nmore values of $a$ , we can increase our\nconfidence in the result.\n\nThis algorithm is known as the Fermat test.\n\nTo implement the Fermat test, we need a function that computes the exponential of a number modulo another number:\n\n```javascript\nexpmod_definition\n expmod_example\n even_definition\n square_definition\n\nfunction expmod(base, exp, m) {\n return exp === 0\n ? 1\n : is_even(exp)\n ? square(expmod(base, exp / 2, m)) % m\n : (base * expmod(base, exp - 1, m)) % m;\n}\n```\n\n```javascript\nexpmod_example\n expmod_definition\n 4\n\nexpmod(4, 3, 5);\n```\n\nThis is very similar to the\nfast_expt\nfunction\nof section.\n\nIt uses successive\nsquaring, so that the number of steps grows logarithmically with the\nexponent.", + "token_count": 290, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1390,8 +1400,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Example_Testing_for_Primality_2" }, { - "content": "This is very similar to the\nfast_expt\nfunction\nof section.\n\nIt uses successive\nsquaring, so that the number of steps grows logarithmically with the\nexponent.\n\nThe Fermat test is performed by choosing at random a number\n$a$ between 1 and\n$n-1$ inclusive and checking whether the remainder\nmodulo $n$ of the\n$n$ th power of $a$ is\nequal to $a$.\n\nThe random number\n$a$ is chosen using the\n\n```javascript\nprimitive function\n\tmath_random,\n```\n\n```javascript\n$n-1$, we multiply\n\tthe return value of math_random by\n\t$n-1$, round down the result with the\n\tprimitive function\n\tmath_floor,\n\tand add 1:\n```\n\n```javascript\nrandom_definition\n\nfunction random(n) {\n return math_floor(math_random() * n);\n}\n```\n\n```javascript\nfermat_test_definition\n square_definition\n expmod_definition\n random_definition\n fermat_test_example\n\nfunction fermat_test(n) {\n function try_it(a) {\n return expmod(a, n, n) === a;\n }\n return try_it(1 + math_floor(math_random() * (n - 1)));\n}\n```\n\n```javascript\nfermat_test_example\n fermat_test_definition\n true\n\nfermat_test(97);\n```\n\nThe following\nfunction\nruns the test a given number of times, as specified by a parameter.\n\nIts\nvalue is true if the test succeeds every time, and false otherwise.\n\n```javascript\nfast_prime_definition\n square_definition\n expmod_definition\n random_definition\n fermat_test_definition\n fast_prime_example\n\nfunction fast_is_prime(n, times) {\n return times === 0\n ? true\n : fermat_test(n)\n ? fast_is_prime(n, times - 1)\n : false;\n}\n```\n\n```javascript\nfast_prime_example\n fast_prime_definition\n true\n\nfast_is_prime(97, 3);\n```\n\nThe Fermat test differs in character from most familiar algorithms, in which\none computes an answer that is guaranteed to be correct.\n\nHere, the answer\nobtained is only probably correct.\n\nMore precisely, if\n$n$ ever fails the Fermat test, we can be certain\nthat $n$ is not prime.\n\nBut the fact that\n$n$ passes the test, while an extremely strong\nindication, is still not a guarantee that $n$ is\nprime.", - "token_count": 277, + "content": "It uses successive\nsquaring, so that the number of steps grows logarithmically with the\nexponent.\n\nThe random number\n$a$ is chosen using the\nprimitive function math_random,\nwhich returns a nonnegative number less than 1.\n\nHence, to obtain a random number between 1 and $n-1$, we multiply the return value of math_random by $n-1$, round down the result with the primitive function math_floor, and add 1:\n\n```javascript\nrandom_definition\n\nfunction random(n) {\n return math_floor(math_random() * n);\n}\n```\n\n```javascript\nfermat_test_definition\n square_definition\n expmod_definition\n random_definition\n fermat_test_example\n\nfunction fermat_test(n) {\n function try_it(a) {\n return expmod(a, n, n) === a;\n }\n return try_it(1 + math_floor(math_random() * (n - 1)));\n}\n```\n\n```javascript\nfermat_test_example\n fermat_test_definition\n true\n\nfermat_test(97);\n```\n\nThe following\nfunction\nruns the test a given number of times, as specified by a parameter.\n\nIts\nvalue is true if the test succeeds every time, and false otherwise.\n\n```javascript\nfast_prime_definition\n square_definition\n expmod_definition\n random_definition\n fermat_test_definition\n fast_prime_example\n\nfunction fast_is_prime(n, times) {\n return times === 0\n ? true\n : fermat_test(n)\n ? fast_is_prime(n, times - 1)\n : false;\n}\n```\n\n```javascript\nfast_prime_example\n fast_prime_definition\n true\n\nfast_is_prime(97, 3);\n```\n\nThe Fermat test differs in character from most familiar algorithms, in which\none computes an answer that is guaranteed to be correct.\n\nHere, the answer\nobtained is only probably correct.\n\nMore precisely, if\n$n$ ever fails the Fermat test, we can be certain\nthat $n$ is not prime.\n\nBut the fact that\n$n$ passes the test, while an extremely strong\nindication, is still not a guarantee that $n$ is\nprime.\n\nWhat we would like to say is that for any number\n$n$ , if we perform the test enough times and find\nthat $n$ always passes the test, then the\nprobability of error in our primality test can be made as small as we like.\n\nUnfortunately, this assertion is not quite correct.", + "token_count": 297, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1400,8 +1410,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Example_Testing_for_Primality_3" }, { - "content": "But the fact that\n$n$ passes the test, while an extremely strong\nindication, is still not a guarantee that $n$ is\nprime.\n\nWhat we would like to say is that for any number\n$n$ , if we perform the test enough times and find\nthat $n$ always passes the test, then the\nprobability of error in our primality test can be made as small as we like.\n\nUnfortunately, this assertion is not quite correct.\n\nThere do exist numbers\nthat fool the Fermat test: numbers $n$ that are\nnot prime and yet have the property that $a^n$ is\ncongruent to $a$ modulo\n$n$ for all integers\n$a < n$.\n\nSuch numbers are extremely rare, so\nthe Fermat test is quite reliable in practice. $n$ by choosing a random integer\n$a < n$ and checking some condition that\ndepends upon $n$ and\n$a$.\n\n(See\nexercise for an example of such a test.)\nOn the other hand, in contrast to the Fermat test, one can prove that, for\nany $n$ , the condition does not hold for most of\nthe integers $a < n$ unless\n$n$ is prime.\n\nThus, if\n$n$ passes the test for some random choice\nof $a$ , the chances are better than even\nthat $n$ is prime.\n\nIf\n$n$ passes the test for two random choices of\n$a$ , the chances are better than 3 out of 4 that\n$n$ is prime.\n\nBy running the test with more and\nmore randomly chosen values of $a$ we can make\nthe probability of error as small as we like.\n\nThe existence of tests for which one can prove that the chance of error\nbecomes arbitrarily small has sparked interest in algorithms of this type,\nwhich have come to be known as probabilistic algorithms.\n\nThere is", - "token_count": 294, + "content": "Unfortunately, this assertion is not quite correct.\n\nSuch numbers are extremely rare, so\nthe Fermat test is quite reliable in practice.\n\nThere are variations of the Fermat test that cannot be fooled.\n\nIn these\ntests, as with the Fermat method, one tests the primality of an integer\n$n$ by choosing a random integer\n$a < n$ and checking some condition that\ndepends upon $n$ and\n$a$.\n\n(See\nexercise for an example of such a test.)\nOn the other hand, in contrast to the Fermat test, one can prove that, for\nany $n$ , the condition does not hold for most of\nthe integers $a < n$ unless\n$n$ is prime.\n\nThus, if\n$n$ passes the test for some random choice\nof $a$ , the chances are better than even\nthat $n$ is prime.\n\nIf\n$n$ passes the test for two random choices of\n$a$ , the chances are better than 3 out of 4 that\n$n$ is prime.\n\nBy running the test with more and\nmore randomly chosen values of $a$ we can make\nthe probability of error as small as we like.\n\nThe existence of tests for which one can prove that the chance of error\nbecomes arbitrarily small has sparked interest in algorithms of this type,\nwhich have come to be known as probabilistic algorithms.\n\nThere is\na great deal of research activity in this area, and probabilistic algorithms\nhave been fruitfully applied to many fields.", + "token_count": 238, "has_code": false, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1410,8 +1420,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Example_Testing_for_Primality_4" }, { - "content": "Consider the problem of computing the exponential of a given number.\n\nWe would like a\nfunction\nthat takes as arguments a base $b$ and a positive\ninteger exponent $n$ and\ncomputes $b^n$.\n\nOne way to do this is via\nthe recursive definition\n\\[\n\\begin{array}{lll}\nb^{n} &=& b\\cdot b^{n-1}\\\\\nb^{0} &=& 1\n\\end{array}\n\\]\nwhich translates readily into the\nfunction\n\n```javascript\nexpt_definition\n expt_example\n\nfunction expt(b, n) {\n return n === 0\n ? 1\n : b * expt(b, n - 1);\n}\n```\n\n```javascript\nexpt_example\n expt_definition\n 81\n\nexpt(3, 4);\n```\n\nThis is a linear recursive process, which requires\n$\\Theta(n)$ steps and\n$\\Theta(n)$ space.\n\nJust as with factorial, we\ncan readily formulate an equivalent linear iteration:\n\n```javascript\nexpt_linear_definition\n expt_example2\n 81\n\nfunction expt(b, n) {\n return expt_iter(b, n, 1);\n}\nfunction expt_iter(b, counter, product) {\n return counter === 0\n ? product\n : expt_iter(b, counter - 1, b * product);\n}\n```\n\n```javascript\nexpt_example2\n\nexpt(3, 4);\n```\n\nThis version requires $\\Theta(n)$ steps and $\\Theta(1)$ space.\n\nWe can compute exponentials in fewer steps by using $b^8$ as \\[ \\begin{array}{l} b\\cdot(b\\cdot(b\\cdot(b\\cdot(b\\cdot(b\\cdot(b\\cdot b)))))) \\end{array} \\] we can compute it using three multiplications: \\[\n\n\\begin{array}{lll} b^{2} &= & b\\cdot b\\\\ b^{4} &= & b^{2}\\cdot b^{2}\\\\ b^{8} &= & b^{4}\\cdot b^{4} \\end{array} \\]\n\nThis method works fine for exponents that are powers of 2.\n\nWe can also take\nadvantage of successive squaring in computing exponentials in general if we\nuse the rule\n\\[\n\\begin{array}{llll}\nb^{n} &=& (b^{n/2})^{2} &\\qquad\\,\\mbox{if}\\ n\\ \\mbox{is even}\\\\\nb^{n} &=& b\\cdot b^{n-1} &\\qquad\\mbox{if}\\ n\\ \\mbox{is odd}\n\\end{array}\n\\]\nWe can express this method as a\nfunction:\n\n```javascript\nexpt_log_definition\n square_definition\n even_definition\n fast_expt_example\n\nfunction fast_expt(b, n) {\n return n === 0\n ? 1\n : is_even(n)\n ? square(fast_expt(b, n / 2))\n : b * fast_expt(b, n - 1);\n}\n```\n\n```javascript\nfast_expt_example\n expt_log_definition\n 81\n\nfast_expt(3, 4);\n```", - "token_count": 297, + "content": "Consider the problem of computing the exponential of a given number.\n\nWe would like a\nfunction\nthat takes as arguments a base $b$ and a positive\ninteger exponent $n$ and\ncomputes $b^n$.\n\nOne way to do this is via\nthe recursive definition\n\\[\n\\begin{array}{lll}\nb^{n} &=& b\\cdot b^{n-1}\\\\\nb^{0} &=& 1\n\\end{array}\n\\]\nwhich translates readily into the\nfunction\n\n```javascript\nexpt_definition\n expt_example\n\nfunction expt(b, n) {\n return n === 0\n ? 1\n : b * expt(b, n - 1);\n}\n```\n\n```javascript\nexpt_example\n expt_definition\n 81\n\nexpt(3, 4);\n```\n\nThis is a linear recursive process, which requires\n$\\Theta(n)$ steps and\n$\\Theta(n)$ space.\n\nJust as with factorial, we\ncan readily formulate an equivalent linear iteration:\n\n```javascript\nexpt_linear_definition\n expt_example2\n 81\n\nfunction expt(b, n) {\n return expt_iter(b, n, 1);\n}\nfunction expt_iter(b, counter, product) {\n return counter === 0\n ? product\n : expt_iter(b, counter - 1, b * product);\n}\n```\n\n```javascript\nexpt_example2\n\nexpt(3, 4);\n```\n\nThis version requires $\\Theta(n)$ steps and $\\Theta(1)$ space.\n\nWe can compute exponentials in fewer steps by using\nsuccessive squaring.\n\nFor instance, rather than computing $b^8$ as\n\\[\n\\begin{array}{l}\nb\\cdot(b\\cdot(b\\cdot(b\\cdot(b\\cdot(b\\cdot(b\\cdot b))))))\n\\end{array}\n\\]\nwe can compute it using three multiplications:\n\\[\n\\begin{array}{lll}\nb^{2} &= & b\\cdot b\\\\\nb^{4} &= & b^{2}\\cdot b^{2}\\\\\nb^{8} &= & b^{4}\\cdot b^{4}\n\\end{array}\n\\]\n\nThis method works fine for exponents that are powers of 2.\n\nWe can also take\nadvantage of successive squaring in computing exponentials in general if we\nuse the rule\n\\[\n\\begin{array}{llll}\nb^{n} &=& (b^{n/2})^{2} &\\qquad\\,\\mbox{if}\\ n\\ \\mbox{is even}\\\\\nb^{n} &=& b\\cdot b^{n-1} &\\qquad\\mbox{if}\\ n\\ \\mbox{is odd}\n\\end{array}\n\\]\nWe can express this method as a\nfunction:\n\n```javascript\nexpt_log_definition\n square_definition\n even_definition\n fast_expt_example\n\nfunction fast_expt(b, n) {\n return n === 0\n ? 1\n : is_even(n)\n ? square(fast_expt(b, n / 2))\n : b * fast_expt(b, n - 1);\n}\n```\n\n```javascript\nfast_expt_example\n expt_log_definition\n 81\n\nfast_expt(3, 4);\n```", + "token_count": 304, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1420,8 +1430,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Exponentiation_1" }, { - "content": "We can also take\nadvantage of successive squaring in computing exponentials in general if we\nuse the rule\n\\[\n\\begin{array}{llll}\nb^{n} &=& (b^{n/2})^{2} &\\qquad\\,\\mbox{if}\\ n\\ \\mbox{is even}\\\\\nb^{n} &=& b\\cdot b^{n-1} &\\qquad\\mbox{if}\\ n\\ \\mbox{is odd}\n\\end{array}\n\\]\nWe can express this method as a\nfunction:\n\nwhere the predicate to test whether an integer is even is defined in terms of the\n\n```javascript\n%,\n\twhich computes the remainder after integer division,\n```\n\nby\n\n```javascript\neven_definition\n even_example\n\nfunction is_even(n) {\n return n % 2 === 0;\n}\n```\n\n```javascript\neven_example\n even_definition\n\nis_even(7);\n```\n\nThe process evolved by\nfast_expt\n$n$ in both space and\nnumber of steps.\n\nTo see this, observe that computing\n$b^{2n}$ using\nfast_expt\nrequires only one more multiplication than computing\n$b^n$.\n\nThe size of the exponent we can compute\ntherefore doubles (approximately) with every new multiplication we are\nallowed.\n\nThus, the number of multiplications required for an exponent of\n$n$ grows about as fast as the logarithm of\n$n$ to the base 2.\n\nThe process has\n$\\Theta(\\log n)$ growth.\n\nThe difference between $\\Theta(\\log n)$ growth\nand $\\Theta(n)$ growth becomes striking as\n$n$ becomes large.\n\nFor example,\nfast_expt\nfor $n=1000$ requires only 14\nmultiplications. ), although, as is\noften the case with iterative algorithms, this is not written down so\nstraightforwardly as the recursive algorithm.", - "token_count": 214, + "content": "We can also take\nadvantage of successive squaring in computing exponentials in general if we\nuse the rule\n\\[\n\\begin{array}{llll}\nb^{n} &=& (b^{n/2})^{2} &\\qquad\\,\\mbox{if}\\ n\\ \\mbox{is even}\\\\\nb^{n} &=& b\\cdot b^{n-1} &\\qquad\\mbox{if}\\ n\\ \\mbox{is odd}\n\\end{array}\n\\]\nWe can express this method as a\nfunction:\n\n```javascript\neven_definition\n even_example\n\nfunction is_even(n) {\n return n % 2 === 0;\n}\n```\n\n```javascript\neven_example\n even_definition\n\nis_even(7);\n```\n\nThe process evolved by\nfast_expt\ngrows logarithmically with $n$ in both space and\nnumber of steps.\n\nTo see this, observe that computing\n$b^{2n}$ using\nfast_expt\nrequires only one more multiplication than computing\n$b^n$.\n\nThe size of the exponent we can compute\ntherefore doubles (approximately) with every new multiplication we are\nallowed.\n\nThus, the number of multiplications required for an exponent of\n$n$ grows about as fast as the logarithm of\n$n$ to the base 2.\n\nThe process has\n$\\Theta(\\log n)$ growth.\n\nThe difference between $\\Theta(\\log n)$ growth\nand $\\Theta(n)$ growth becomes striking as\n$n$ becomes large.\n\nFor example,\nfast_expt\nfor $n=1000$ requires only 14\nmultiplications.\n\nIt is also possible to use the idea of successive squaring to devise an\niterative algorithm that computes exponentials with a logarithmic number of\nsteps (see exercise ), although, as is\noften the case with iterative algorithms, this is not written down so\nstraightforwardly as the recursive algorithm.", + "token_count": 217, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1430,8 +1440,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Exponentiation_2" }, { - "content": "The greatest common divisor (GCD) of two integers\n$a$ and $b$ is defined\nto be the largest integer that divides both $a$\nand $b$ with no remainder.\n\nFor example, the GCD\nof 16 and 28 is 4.\n\nIn chapter , when we investigate how to\nimplement rational-number arithmetic, we will need to be able to compute\nGCDs in order to reduce rational numbers to lowest terms.\n\n(To reduce a\nrational number to lowest terms, we must divide both the numerator and the\ndenominator by their GCD.\n\nFor example, 16/28 reduces to 4/7.) One way to\nfind the GCD of two integers is to factor them and search for common\nfactors, but there is a famous algorithm that is much more efficient.\n\n$r$ is the remainder when\n$a$ is divided by\n$b$ , then the common divisors of\n$a$ and $b$ are\nprecisely the same as the common divisors of $b$\nand $r$.\n\nThus, we can use the equation\n\\[\\begin{array}{lll}\n\\textrm{GCD} (a, b) &=& \\textrm{GCD}(b, r)\n\\end{array}\\]\nto successively reduce the problem of computing a GCD to the problem of\ncomputing the GCD of smaller and smaller pairs of integers.\n\nFor example,\n\\[\\begin{array}{lll}\n\\textrm{GCD}(206,40) & = & \\textrm{GCD}(40,6) \\\\\n& = & \\textrm{GCD}(6,4) \\\\\n& = & \\textrm{GCD}(4,2) \\\\\n& = & \\textrm{GCD}(2,0) \\\\\n& = & 2\n\\end{array}\\]\nreduces $\\textrm{GCD}(206, 40)$ to\n$\\textrm{GCD}(2, 0)$ , which is 2.\n\nIt is\npossible to show that starting with any two positive integers and\nperforming repeated reductions will always eventually produce a pair\nwhere the second number is 0.\n\nThen the GCD is the other\nnumber in the pair.\n\nThis method for computing the GCD is\nknown as Euclid s Algorithm.\n\nIt is easy to express Euclid s Algorithm as a function:", - "token_count": 289, + "content": "The greatest common divisor (GCD) of two integers\n$a$ and $b$ is defined\nto be the largest integer that divides both $a$\nand $b$ with no remainder.\n\nFor example, the GCD\nof 16 and 28 is 4.\n\nIn chapter , when we investigate how to\nimplement rational-number arithmetic, we will need to be able to compute\nGCDs in order to reduce rational numbers to lowest terms.\n\n(To reduce a\nrational number to lowest terms, we must divide both the numerator and the\ndenominator by their GCD.\n\nFor example, 16/28 reduces to 4/7.) One way to\nfind the GCD of two integers is to factor them and search for common\nfactors, but there is a famous algorithm that is much more efficient.\n\nThe idea of the algorithm is based on the observation that, if\n$r$ is the remainder when\n$a$ is divided by\n$b$ , then the common divisors of\n$a$ and $b$ are\nprecisely the same as the common divisors of $b$\nand $r$.\n\nThus, we can use the equation\n\\[\\begin{array}{lll}\n\\textrm{GCD} (a, b) &=& \\textrm{GCD}(b, r)\n\\end{array}\\]\nto successively reduce the problem of computing a GCD to the problem of\ncomputing the GCD of smaller and smaller pairs of integers.\n\nFor example,\n\\[\\begin{array}{lll}\n\\textrm{GCD}(206,40) & = & \\textrm{GCD}(40,6) \\\\\n& = & \\textrm{GCD}(6,4) \\\\\n& = & \\textrm{GCD}(4,2) \\\\\n& = & \\textrm{GCD}(2,0) \\\\\n& = & 2\n\\end{array}\\]\nreduces $\\textrm{GCD}(206, 40)$ to\n$\\textrm{GCD}(2, 0)$ , which is 2.\n\nIt is\npossible to show that starting with any two positive integers and\nperforming repeated reductions will always eventually produce a pair\nwhere the second number is 0.\n\nThen the GCD is the other\nnumber in the pair.\n\nThis method for computing the GCD is\nknown as Euclid s Algorithm.", + "token_count": 290, "has_code": false, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1440,8 +1450,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Greatest_Common_Divisors_1" }, { - "content": "It is easy to express Euclid s Algorithm as a function:\n\n```javascript\ngcd_definition\n gcd_example\n\nfunction gcd(a, b) {\n return b === 0 ? a : gcd(b, a % b);\n}\n```\n\n```javascript\ngcd_example\n gcd_definition\n 4\n\ngcd(20, 12);\n```\n\nThis generates an iterative process, whose number of steps grows as the logarithm of the numbers involved.\n\nThe fact that the number of steps required by Euclid s Algorithm has Lam s Theorem: s Algorithm requires $k$ steps to compute the GCD\n\nof some pair, then the smaller number in the pair must be greater than or equal to the $k$ th Fibonacci number.\n\nWe can use this theorem to get an order-of-growth estimate for Euclid s\nAlgorithm.\n\nLet $n$ be the smaller of the two\ninputs to the\nfunction.\n\nIf the process takes $k$ steps, then we must have\n$n\\geq {\\textrm{Fib}} (k)\\approx\\phi^k/\\sqrt{5}$.\n\nTherefore the number of steps $k$ grows as the\nlogarithm (to the base $\\phi$ ) of\n$n$.\n\nHence, the order of growth is\n$\\Theta(\\log n)$.", - "token_count": 166, + "content": "This method for computing the GCD is\nknown as Euclid s Algorithm.\n\n```javascript\ngcd_definition\n gcd_example\n\nfunction gcd(a, b) {\n return b === 0 ? a : gcd(b, a % b);\n}\n```\n\n```javascript\ngcd_example\n gcd_definition\n 4\n\ngcd(20, 12);\n```\n\nThis generates an iterative process, whose number of steps grows as the logarithm of the numbers involved.\n\nThe fact that the number of steps required by Euclid s Algorithm has logarithmic growth bears an interesting relation to the Fibonacci numbers: Lam s\n\nTheorem: If Euclid s Algorithm requires $k$ steps to compute the GCD of some pair, then the smaller number in the pair must be greater\n\nthan or equal to the $k$ th Fibonacci number.\n\nWe can use this theorem to get an order-of-growth estimate for Euclid s\nAlgorithm.\n\nLet $n$ be the smaller of the two\ninputs to the\nfunction.\n\nIf the process takes $k$ steps, then we must have\n$n\\geq {\\textrm{Fib}} (k)\\approx\\phi^k/\\sqrt{5}$.\n\nTherefore the number of steps $k$ grows as the\nlogarithm (to the base $\\phi$ ) of\n$n$.\n\nHence, the order of growth is\n$\\Theta(\\log n)$.", + "token_count": 179, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1450,8 +1460,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Greatest_Common_Divisors_2" }, { - "content": "We begin by considering the\n\\[\n\\begin{array}{lll}\nn! &=& n\\cdot(n-1)\\cdot(n-2)\\cdots3\\cdot2\\cdot1\n\\end{array}\n\\]\nThere are many ways to compute factorials.\n\nOne way is to make use of\nthe observation that $n!$ is equal to\n$n$ times $(n-1)!$ for\nany positive integer $n$ :\n\\[\n\\begin{array}{lll}\nn! &=& n\\cdot\\left[(n-1)\\cdot(n-2)\\cdots3\\cdot2\\cdot1\\right] \\quad = \\quad n \\cdot(n-1)!\n\\end{array}\n\\]\nThus, we can compute $n!$ by computing\n$(n-1)!$ and multiplying the\nresult by $n$.\n\nIf we add the stipulation that 1!\nis equal to 1,\nthis observation translates directly into a\ncomputer function:\n\n```javascript\nfactorial_definition\n factorial_example\n 120\n\nfunction factorial(n) {\n return n === 1\n ? 1\n : n * factorial(n - 1);\n}\n```\n\n```javascript\nfactorial_example\n\nfactorial(5);\n```\n\n```javascript\nWe can use the substitution model of\n section to watch this\n .\n```\n\nNow let s take a different perspective on computing factorials.\n\nWe\ncould describe a rule for computing $n!$ by\nspecifying that we first multiply 1 by 2, then multiply the result by 3,\nthen by 4, and so on until we reach $n$.\n\nMore formally, we maintain a running product, together with a counter\nthat counts from 1 up to $n$.\n\nWe can describe\nthe computation by saying that the counter and the product simultaneously\nchange from one step to the next according to the rule\n\\[\n\\begin{array}{lll}\n\\textrm{product} & \\leftarrow & \\textrm{counter} \\cdot \\textrm{product}\\\\\n\\textrm{counter} & \\leftarrow & \\textrm{counter} + 1\n\\end{array}\n\\]\nand stipulating that $n!$ is the value of the\nproduct when the counter exceeds $n$.\n\nOnce again, we can recast our description as a function for computing factorials:\n\n```javascript\nfactorial_iterative_definition\n factorial_example\n\nfunction factorial(n) {\n return fact_iter(1, 1, n);\n}\nfunction fact_iter(product, counter, max_count) {\n return counter > max_count\n ? product\n : fact_iter(counter * product,\n counter + 1,\n max_count);\n}\n```\n\nAs before, we can use the substitution model to visualize the process", - "token_count": 302, + "content": "We begin by considering the\nfactorial function, defined by\n\\[\n\\begin{array}{lll}\nn! &=& n\\cdot(n-1)\\cdot(n-2)\\cdots3\\cdot2\\cdot1\n\\end{array}\n\\]\nThere are many ways to compute factorials.\n\nOne way is to make use of\nthe observation that $n!$ is equal to\n$n$ times $(n-1)!$ for\nany positive integer $n$ :\n\\[\n\\begin{array}{lll}\nn! &=& n\\cdot\\left[(n-1)\\cdot(n-2)\\cdots3\\cdot2\\cdot1\\right] \\quad = \\quad n \\cdot(n-1)!\n\\end{array}\n\\]\nThus, we can compute $n!$ by computing\n$(n-1)!$ and multiplying the\nresult by $n$.\n\nIf we add the stipulation that 1!\nis equal to 1,\nthis observation translates directly into a\ncomputer function:\n\n```javascript\nfactorial_definition\n factorial_example\n 120\n\nfunction factorial(n) {\n return n === 1\n ? 1\n : n * factorial(n - 1);\n}\n```\n\n```javascript\nfactorial_example\n\nfactorial(5);\n```\n\nWe can use the substitution model of section to watch this function in action computing 6!, as shown in figure.\n\nNow let s take a different perspective on computing factorials.\n\nWe\ncould describe a rule for computing $n!$ by\nspecifying that we first multiply 1 by 2, then multiply the result by 3,\nthen by 4, and so on until we reach $n$.\n\nMore formally, we maintain a running product, together with a counter\nthat counts from 1 up to $n$.\n\nWe can describe\nthe computation by saying that the counter and the product simultaneously\nchange from one step to the next according to the rule\n\\[\n\\begin{array}{lll}\n\\textrm{product} & \\leftarrow & \\textrm{counter} \\cdot \\textrm{product}\\\\\n\\textrm{counter} & \\leftarrow & \\textrm{counter} + 1\n\\end{array}\n\\]\nand stipulating that $n!$ is the value of the\nproduct when the counter exceeds $n$.\n\nOnce again, we can recast our description as a function for computing factorials:\n\n```javascript\nfactorial_iterative_definition\n factorial_example\n\nfunction factorial(n) {\n return fact_iter(1, 1, n);\n}\nfunction fact_iter(product, counter, max_count) {\n return counter > max_count\n ? product\n : fact_iter(counter * product,\n counter + 1,\n max_count);\n}\n```", + "token_count": 300, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1460,9 +1470,9 @@ "chunk_id": "Building_Abstractions_with_Functions_Linear_Recursion_and_Iteration_1" }, { - "content": "As before, we can use the substitution model to visualize the process\n\n```javascript\nA linear recursive process for computing 6!.\n\n\tA linear iterative process for computing\n\t$6!$.\n\n of computing $6!$, as shown in\n\tfigure.\n```\n\nCompare the two processes.\n\nFrom one point of view, they seem hardly\ndifferent at all.\n\nBoth compute the same mathematical function on the\nsame domain, and each requires a number of steps proportional to\n$n$\nto compute $n!$.\n\nIndeed, both processes even\ncarry out the same sequence of multiplications, obtaining the same sequence\nof partial products.\n\nOn the other hand, when we consider the\nshapes of the two processes, we find that they evolve quite\ndifferently.\n\nConsider the first process.\n\nThe substitution model reveals a shape of\nexpansion followed by contraction, indicated by the arrow in\nfigure.\n\nThe expansion occurs as the process builds up a chain of\ndeferred operations (in this case, a chain of multiplications).\n\nThe contraction occurs as the operations are actually performed.\n\nThis\ntype of process, characterized by a chain of deferred operations, is called a\nrecursive process.\n\nCarrying out this process requires that the\ninterpreter keep track of the operations to be performed later on.\n\nIn the\ncomputation of $n!$ , the length of the chain of\ndeferred multiplications, and hence the amount of information needed to\nkeep track of it,\n$n$ (is proportional to\n$n$ ), just like the number of steps.\n\nSuch a process is called a\nlinear recursive process.\n\nBy contrast, the second process does not grow and shrink.\n\nAt each\nstep, all we need to keep track of, for any $n$ ,\nare the current values of the\nnames\nmax_count.\n\nWe call this an\niterative process.", - "token_count": 280, - "has_code": true, + "content": "Once again, we can recast our description as a function for computing factorials:\n\nA linear iterative process for computing $6!$. of computing $6!$, as shown in figure.\n\nCompare the two processes.\n\nFrom one point of view, they seem hardly\ndifferent at all.\n\nBoth compute the same mathematical function on the\nsame domain, and each requires a number of steps proportional to\n$n$\nto compute $n!$.\n\nIndeed, both processes even\ncarry out the same sequence of multiplications, obtaining the same sequence\nof partial products.\n\nOn the other hand, when we consider the\nshapes of the two processes, we find that they evolve quite\ndifferently.\n\nConsider the first process.\n\nThe substitution model reveals a shape of\nexpansion followed by contraction, indicated by the arrow in\nfigure.\n\nThe expansion occurs as the process builds up a chain of\ndeferred operations (in this case, a chain of multiplications).\n\nThe contraction occurs as the operations are actually performed.\n\nThis\ntype of process, characterized by a chain of deferred operations, is called a\nrecursive process.\n\nCarrying out this process requires that the\ninterpreter keep track of the operations to be performed later on.\n\nIn the\ncomputation of $n!$ , the length of the chain of\ndeferred multiplications, and hence the amount of information needed to\nkeep track of it,\ngrows linearly with $n$ (is proportional to\n$n$ ), just like the number of steps.\n\nSuch a process is called a\nlinear recursive process.\n\nBy contrast, the second process does not grow and shrink.\n\nAt each\nstep, all we need to keep track of, for any $n$ ,\nare the current values of the\nnames\n, ,\nand\nmax_count.\n\nWe call this an\niterative process.", + "token_count": 278, + "has_code": false, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", "subsection": "Linear Recursion and Iteration", @@ -1470,8 +1480,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Linear_Recursion_and_Iteration_2" }, { - "content": "We call this an\niterative process.\n\nIn general, an iterative process is one whose\nstate can be summarized by a fixed number of\nstate variables , together with a fixed rule that describes how\nthe state variables should be updated as the process moves from state to\nstate and an (optional) end test that specifies conditions under which the\nprocess should terminate.\n\nIn computing $n!$ , the\nnumber of steps required grows linearly with $n$.\n\nSuch a process is called a\nlinear iterative process.\n\nThe contrast between the two processes can be seen in another way.\n\nIn the iterative case, the state variables provide a complete description of\nthe state of the process at any point.\n\nIf we stopped the computation between\nsteps, all we would need to do to resume the computation is to supply the\ninterpreter with the values of the three state variables.\n\nNot so with the\nrecursive process.\n\nIn this case there is some additional\nhidden information, maintained by the interpreter and not\ncontained in the state variables, which indicates where the process\nis in negotiating the chain of deferred operations.\n\nThe longer the\nchain, the more information must be maintained.\n\nIn contrasting iteration and recursion, we must be careful not to\nconfuse the notion of a\nprocess with the notion of a recursive\nfunction.\n\nWhen we describe a\nfunction\nas recursive, we are referring to the syntactic fact that the\nfunction declaration\nrefers (either directly or indirectly) to the\nfunction\nitself.\n\nBut when we describe a process as following a pattern that is, say,\nlinearly recursive, we are speaking about how the process evolves, not\nabout the syntax of how a\nfunction\nis written.\n\nIt may seem disturbing that we refer to a recursive\nfunction\nsuch as\nfact_iter\nas generating an iterative process.", - "token_count": 298, + "content": "We call this an\niterative process.\n\nIn computing $n!$ , the\nnumber of steps required grows linearly with $n$.\n\nSuch a process is called a\nlinear iterative process.\n\nThe contrast between the two processes can be seen in another way.\n\nIn the iterative case, the state variables provide a complete description of\nthe state of the process at any point.\n\nIf we stopped the computation between\nsteps, all we would need to do to resume the computation is to supply the\ninterpreter with the values of the three state variables.\n\nNot so with the\nrecursive process.\n\nIn this case there is some additional\nhidden information, maintained by the interpreter and not\ncontained in the state variables, which indicates where the process\nis in negotiating the chain of deferred operations.\n\nThe longer the\nchain, the more information must be maintained.\n\nIn contrasting iteration and recursion, we must be careful not to\nconfuse the notion of a\nrecursive process with the notion of a recursive\nfunction.\n\nWhen we describe a\nfunction\nas recursive, we are referring to the syntactic fact that the\nfunction declaration\nrefers (either directly or indirectly) to the\nfunction\nitself.\n\nBut when we describe a process as following a pattern that is, say,\nlinearly recursive, we are speaking about how the process evolves, not\nabout the syntax of how a\nfunction\nis written.\n\nIt may seem disturbing that we refer to a recursive\nfunction\nsuch as\nfact_iter\nas generating an iterative process.\n\nHowever, the process really is\niterative: Its state is captured completely by its three state variables,\nand an interpreter need keep track of only three\nnames\nin order to execute the process.", + "token_count": 275, "has_code": false, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1480,9 +1490,9 @@ "chunk_id": "Building_Abstractions_with_Functions_Linear_Recursion_and_Iteration_3" }, { - "content": "It may seem disturbing that we refer to a recursive\nfunction\nsuch as\nfact_iter\nas generating an iterative process.\n\nHowever, the process really is\niterative: Its state is captured completely by its three state variables,\nand an interpreter need keep track of only three\nnames\nin order to execute the process.\n\nOne reason that the distinction between process and\nfunction\nmay be confusing is that most implementations of common languages\n(including\nare designed in such a way that the interpretation of\nany recursive\nfunction\nconsumes an amount of memory that grows with the number of\nfunction\ncalls, even when the process described is, in principle, iterative.\n\nAs a consequence, these languages can describe iterative processes only\nby resorting to special-purpose\nlooping constructs such as\n$\\texttt{do}$ ,\n$\\texttt{repeat}$ ,\n$\\texttt{until}$ ,\n$\\texttt{for}$ , and\n$\\texttt{while}$.\n\nThe implementation of\nJavaScript\nwe shall consider in chapter does not share this defect.\n\nIt will\nexecute an iterative process in constant space, even if the iterative\nprocess is described by a recursive\nfunction.\n\n```javascript\nAn implementation with this property is called\n\ttail-recursive.\n```", - "token_count": 178, - "has_code": true, + "content": "However, the process really is\niterative: Its state is captured completely by its three state variables,\nand an interpreter need keep track of only three\nnames\nin order to execute the process.\n\nAs a consequence, these languages can describe iterative processes only\nby resorting to special-purpose\nlooping constructs such as\n$\\texttt{do}$ ,\n$\\texttt{repeat}$ ,\n$\\texttt{until}$ ,\n$\\texttt{for}$ , and\n$\\texttt{while}$.\n\nThe implementation of\nJavaScript\nwe shall consider in chapter does not share this defect.\n\nIt will\nexecute an iterative process in constant space, even if the iterative\nprocess is described by a recursive\nfunction.\n\nAn implementation with this property is called tail-recursive.\n\nWith a tail-recursive implementation, iteration can be expressed using the ordinary function call mechanism, so that special iteration constructs are useful only as syntactic sugar.", + "token_count": 127, + "has_code": false, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", "subsection": "Linear Recursion and Iteration", @@ -1490,8 +1500,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Linear_Recursion_and_Iteration_4" }, { - "content": "The previous examples illustrate that processes can differ\nconsiderably in the rates at which they consume computational\nresources.\n\nOne convenient way to describe this difference is to use\nthe notion of\norder of growth to obtain a gross measure of the\n\nLet $n$ be a parameter that measures the size of\nthe problem, and let $R(n)$ be the amount\nof resources the process requires for a problem of size\n$n$.\n\nIn our previous examples we took\n$n$ to be the number for which a given\nfunction is to be computed, but there are other possibilities.\n\nFor instance, if our goal is to compute an approximation to the\nsquare root of a number, we might take\n$n$ to be the number of digits accuracy required.\n\nFor matrix multiplication we might take $n$ to\nbe the number of rows in the matrices.\n\nIn general there are a number of\nproperties of the problem with respect to which it will be desirable to\nanalyze a given process.\n\nSimilarly, $R(n)$\nmight measure the number of internal storage registers used, the\nnumber of elementary machine operations performed, and so on.\n\nIn\ncomputers that do only a fixed number of operations at a time, the\ntime required will be proportional to the number of elementary machine\noperations performed.\n\nWe say that $R(n)$ has order of growth\n$\\Theta(f(n))$ , written\n$R(n)=\\Theta(f(n))$ (pronounced\ntheta of $f(n)$ ), if there are\npositive constants $k_1$ and\n$k_2$ independent of\n$n$ such that\n\\[\n\\begin{array}{lllll}\nk_1\\,f(n) & \\leq & R(n) & \\leq & k_2\\,f(n)\n\\end{array}\n\\]\nfor any sufficiently large value of $n$.\n\n(In other words, for large $n$ ,\nthe value $R(n)$ is sandwiched between\n$k_1f(n)$ and\n$k_2f(n)$.)\n\nthe\nnumber of steps grows proportionally to the input\n$n$.\n\nThus, the steps required for this process\ngrows as $\\Theta(n)$.", - "token_count": 298, + "content": "The previous examples illustrate that processes can differ\nconsiderably in the rates at which they consume computational\nresources.\n\nOne convenient way to describe this difference is to use\nthe notion of\norder of growth to obtain a gross measure of the\nresources required by a process as the inputs become larger.\n\nLet $n$ be a parameter that measures the size of\nthe problem, and let $R(n)$ be the amount\nof resources the process requires for a problem of size\n$n$.\n\nIn our previous examples we took\n$n$ to be the number for which a given\nfunction is to be computed, but there are other possibilities.\n\nFor instance, if our goal is to compute an approximation to the\nsquare root of a number, we might take\n$n$ to be the number of digits accuracy required.\n\nFor matrix multiplication we might take $n$ to\nbe the number of rows in the matrices.\n\nIn general there are a number of\nproperties of the problem with respect to which it will be desirable to\nanalyze a given process.\n\nSimilarly, $R(n)$\nmight measure the number of internal storage registers used, the\nnumber of elementary machine operations performed, and so on.\n\nIn\ncomputers that do only a fixed number of operations at a time, the\ntime required will be proportional to the number of elementary machine\noperations performed.\n\nWe say that $R(n)$ has order of growth\n$\\Theta(f(n))$ , written\n$R(n)=\\Theta(f(n))$ (pronounced\ntheta of $f(n)$ ), if there are\npositive constants $k_1$ and\n$k_2$ independent of\n$n$ such that\n\\[\n\\begin{array}{lllll}\nk_1\\,f(n) & \\leq & R(n) & \\leq & k_2\\,f(n)\n\\end{array}\n\\]\nfor any sufficiently large value of $n$.\n\n(In other words, for large $n$ ,\nthe value $R(n)$ is sandwiched between\n$k_1f(n)$ and\n$k_2f(n)$.)", + "token_count": 288, "has_code": false, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1500,8 +1510,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Orders_of_Growth_1" }, { - "content": "Thus, the steps required for this process\ngrows as $\\Theta(n)$.\n\nWe also saw that the space\nrequired grows as $\\Theta(n)$.\n\nFor the\n$\\Theta(n)$ but the space is\n$\\Theta(1)$ that is,\nconstant. $\\Theta(\\phi^{n})$ steps and space\n$\\Theta(n)$ , where\n$\\phi$ is the golden ratio described in\nsection.\n\nOrders of growth provide only a crude description of the behavior of a\nprocess.\n\nFor example, a process requiring $n^2$\nsteps and a process requiring $1000n^2$ steps and\na process requiring $3n^2+10n+17$ steps all have\n$\\Theta(n^2)$ order of growth.\n\nOn the other hand,\norder of growth provides a useful indication of how we may expect the\nbehavior of the process to change as we change the size of the problem.\n\nFor a\n$\\Theta(n)$ (linear) process, doubling the size\nwill roughly double the amount of resources used.\n\nFor an\nwe will examine two algorithms whose order of growth is\n\n-\n- The function p\nwill call itself recursively as long as the angle value is greater\nthan 0.1.\n\nThere will be altogether 5 calls of\np , with arguments 12.15, 4.05,\n1.35, 0.45, 0.15 and 0.05.\n-\n-\nThe function sine gives\nrise to a recursive process.\n\nIn each recursive call, the\nangle is divided by 3\nuntil its absolute value is smaller than 0.1.\n\nThus the number of steps and the space required has an order\nof growth of $O(\\log a)$.\n\nNote that the base of the logarithm\nis immaterial for the order of growth because the logarithms\nof different bases differ only by a constant factor.", - "token_count": 253, + "content": "(In other words, for large $n$ ,\nthe value $R(n)$ is sandwiched between\n$k_1f(n)$ and\n$k_2f(n)$.)\n\nThus, the steps required for this process\ngrows as $\\Theta(n)$.\n\nWe also saw that the space\nrequired grows as $\\Theta(n)$.\n\nFor the\niterative factorial, the number of steps is still\n$\\Theta(n)$ but the space is\n$\\Theta(1)$ that is,\nconstant.\n\nThe\ntree-recursive Fibonacci computation requires\n$\\Theta(\\phi^{n})$ steps and space\n$\\Theta(n)$ , where\n$\\phi$ is the golden ratio described in\nsection.\n\nOrders of growth provide only a crude description of the behavior of a\nprocess.\n\nFor example, a process requiring $n^2$\nsteps and a process requiring $1000n^2$ steps and\na process requiring $3n^2+10n+17$ steps all have\n$\\Theta(n^2)$ order of growth.\n\nOn the other hand,\norder of growth provides a useful indication of how we may expect the\nbehavior of the process to change as we change the size of the problem.\n\nFor a\n$\\Theta(n)$ (linear) process, doubling the size\nwill roughly double the amount of resources used.\n\nFor an\nexponential process, each increment in problem size will multiply the\nresource utilization by a constant factor.\n\nIn the remainder of\nsection\nwe will examine two algorithms whose order of growth is\nlogarithmic, so that doubling the problem size increases the resource\nrequirement by a constant amount.\n\n-\n- The function p\nwill call itself recursively as long as the angle value is greater\nthan 0.1.\n\nThere will be altogether 5 calls of\np , with arguments 12.15, 4.05,\n1.35, 0.45, 0.15 and 0.05.\n-\n-\nThe function sine gives\nrise to a recursive process.\n\nIn each recursive call, the\nangle is divided by 3\nuntil its absolute value is smaller than 0.1.\n\nThus the number of steps and the space required has an order\nof growth of $O(\\log a)$.", + "token_count": 292, "has_code": false, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1510,8 +1520,18 @@ "chunk_id": "Building_Abstractions_with_Functions_Orders_of_Growth_2" }, { - "content": "Another common pattern of computation is called tree recursion.\n\nAs an example, consider computing the sequence of\n\\[\\begin{array}{l}\n0, 1, 1, 2, 3, 5, 8, 13, 21, \\ldots\n\\end{array}\\]\nIn general, the Fibonacci numbers can be defined by the rule\n\\[\\begin{array}{lll}\n\\textrm{Fib}(n) & = & \\left\\{ \\begin{array}{ll}\n0 & \\mbox{if $n=0$}\\\\\n1 & \\mbox{if $n=1$}\\\\\n\\textrm{Fib}(n-1)+\\textrm{Fib}(n-2) & \\mbox{otherwise}\n\\end{array}\n\\right.\n\\end{array}\\]\nWe can immediately translate this definition into a recursive\nfunction\nfor computing Fibonacci numbers:\n\n```javascript\nfib_definition\n fib_example\n\nfunction fib(n) {\n return n === 0\n ? 0\n : n === 1\n ? 1\n : fib(n - 1) + fib(n - 2);\n}\n```\n\n```javascript\nfib_example\n fib_definition\n 8\n\nfib(6);\n```\n\n```javascript\nThe tree-recursive process generated in computing\n\t fib(5).\n```\n\nConsider the pattern of this computation.\n\nTo compute\nfib(5),\nwe compute\nfib(4)\nand\nfib(3).\n\nTo compute\nfib(4),\nwe compute\nfib(3)\nand\nfib(2).\n\nIn general, the evolved process looks like a tree, as shown in\nfigure.\n\nNotice that the branches split into\ntwo at each level (except at the bottom); this reflects the fact that the\nfunction\ncalls itself twice each time it is invoked.\n\nThis\nfunction\nis instructive as a prototypical tree recursion, but it is a terrible way to\ncompute Fibonacci numbers because it does so much redundant computation.\n\nNotice in\nfigure\nthat the entire\ncomputation of\n\n```javascript\nfib(3)almost\n\thalf the workis\n```\n\nduplicated.\n\nIn fact, it is not hard to show that the number of times the\nfunction\nwill compute\nfib(1)\nor\nfib(0)\n(the number of leaves in the above tree, in general) is precisely\n$\\textrm{Fib}(n+1)$.\n\nTo get an idea of how\nbad this is, one can show that the value of\n$\\textrm{Fib}(n)$\n$n$.", - "token_count": 274, + "content": "Thus the number of steps and the space required has an order\nof growth of $O(\\log a)$.", + "token_count": 17, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "Functions and the Processes They Generate", + "subsection": "Orders of Growth", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Functions_Orders_of_Growth_3" + }, + { + "content": "Another common pattern of computation is called tree recursion.\n\nAs an example, consider computing the sequence of\nFibonacci numbers,\nin which each number is the sum of the preceding two:\n\\[\\begin{array}{l}\n0, 1, 1, 2, 3, 5, 8, 13, 21, \\ldots\n\\end{array}\\]\nIn general, the Fibonacci numbers can be defined by the rule\n\\[\\begin{array}{lll}\n\\textrm{Fib}(n) & = & \\left\\{ \\begin{array}{ll}\n0 & \\mbox{if $n=0$}\\\\\n1 & \\mbox{if $n=1$}\\\\\n\\textrm{Fib}(n-1)+\\textrm{Fib}(n-2) & \\mbox{otherwise}\n\\end{array}\n\\right.\n\\end{array}\\]\nWe can immediately translate this definition into a recursive\nfunction\nfor computing Fibonacci numbers:\n\n```javascript\nfib_definition\n fib_example\n\nfunction fib(n) {\n return n === 0\n ? 0\n : n === 1\n ? 1\n : fib(n - 1) + fib(n - 2);\n}\n```\n\n```javascript\nfib_example\n fib_definition\n 8\n\nfib(6);\n```\n\nThe tree-recursive process generated in computing fib(5).\n\nConsider the pattern of this computation.\n\nTo compute\nfib(5),\nwe compute\nfib(4)\nand\nfib(3).\n\nTo compute\nfib(4),\nwe compute\nfib(3)\nand\nfib(2).\n\nIn general, the evolved process looks like a tree, as shown in\nfigure.\n\nNotice that the branches split into\ntwo at each level (except at the bottom); this reflects the fact that the\nfunction\ncalls itself twice each time it is invoked.\n\nThis\nfunction\nis instructive as a prototypical tree recursion, but it is a terrible way to\ncompute Fibonacci numbers because it does so much redundant computation.\n\nNotice in\nfigure\nthat the entire\ncomputation of\nfib(3)almost half the workis\nduplicated.\n\nIn fact, it is not hard to show that the number of times the\nfunction\nwill compute\nfib(1)\nor\nfib(0)\n(the number of leaves in the above tree, in general) is precisely\n$\\textrm{Fib}(n+1)$.\n\nTo get an idea of how\nbad this is, one can show that the value of\n$\\textrm{Fib}(n)$\ngrows exponentially with $n$.", + "token_count": 286, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1520,8 +1540,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Tree_Recursion_1" }, { - "content": "To get an idea of how\nbad this is, one can show that the value of\n$\\textrm{Fib}(n)$\n$n$.\n\nMore precisely\n(see exercise ),\n$\\textrm{Fib}(n)$ is the closest integer to\n$\\phi^{n} /\\sqrt{5}$ , where\n\\[\\begin{array}{lllll}\n\\phi&=&(1+\\sqrt{5})/2 & \\approx & 1.6180\n\\end{array}\\]\nis the\ngolden ratio , which satisfies the equation\n\\[\\begin{array}{lll}\n\\phi^{2} &=&\\phi + 1\n\\end{array}\\]\nThus, the process uses a number of steps that grows exponentially with the\ninput.\n\nOn the other hand, the space required grows only linearly with the\ninput, because we need keep track only of which nodes are above us in the\ntree at any point in the computation.\n\nIn general, the number of steps\nrequired by a tree-recursive process will be proportional to the number of\nnodes in the tree, while the space required will be proportional to the\nmaximum depth of the tree.\n\nWe can also formulate an iterative process for computing the Fibonacci\nnumbers.\n\nThe idea is to use a pair of integers $a$\nand $b$ , initialized to\n$\\textrm{Fib}(1)=1$ and\n$\\textrm{Fib}(0)=0$ , and to repeatedly apply the\nsimultaneous transformations\n\\[\\begin{array}{lll}\na & \\leftarrow & a+b \\\\\nb & \\leftarrow & a\n\\end{array}\\]\nIt is not hard to show that, after applying this transformation\n$n$ times, $a$ and\n$b$ will be equal, respectively, to\n$\\textrm{Fib}(n+1)$ and\n$\\textrm{Fib}(n)$.\n\nThus, we can compute\nFibonacci numbers iteratively using the\nfunction\n\n```javascript\nfib_example\n 8\n\nfunction fib(n) {\n return fib_iter(1, 0, n);\n}\nfunction fib_iter(a, b, count) {\n return count === 0\n ? b\n : fib_iter(a + b, a, count - 1);\n}\n```\n\nThis second method for computing $\\textrm{Fib}(n)$\nis a linear iteration.\n\nThe difference in number of steps required by the two\nmethods one linear in $n$ , one growing as\nfast as $\\textrm{Fib}(n)$ itself is\nenormous, even for small inputs.", - "token_count": 295, + "content": "To get an idea of how\nbad this is, one can show that the value of\n$\\textrm{Fib}(n)$\ngrows exponentially with $n$.\n\nOn the other hand, the space required grows only linearly with the\ninput, because we need keep track only of which nodes are above us in the\ntree at any point in the computation.\n\nIn general, the number of steps\nrequired by a tree-recursive process will be proportional to the number of\nnodes in the tree, while the space required will be proportional to the\nmaximum depth of the tree.\n\nWe can also formulate an iterative process for computing the Fibonacci\nnumbers.\n\nThe idea is to use a pair of integers $a$\nand $b$ , initialized to\n$\\textrm{Fib}(1)=1$ and\n$\\textrm{Fib}(0)=0$ , and to repeatedly apply the\nsimultaneous transformations\n\\[\\begin{array}{lll}\na & \\leftarrow & a+b \\\\\nb & \\leftarrow & a\n\\end{array}\\]\nIt is not hard to show that, after applying this transformation\n$n$ times, $a$ and\n$b$ will be equal, respectively, to\n$\\textrm{Fib}(n+1)$ and\n$\\textrm{Fib}(n)$.\n\nThus, we can compute\nFibonacci numbers iteratively using the\nfunction\n\n```javascript\nfib_example\n 8\n\nfunction fib(n) {\n return fib_iter(1, 0, n);\n}\nfunction fib_iter(a, b, count) {\n return count === 0\n ? b\n : fib_iter(a + b, a, count - 1);\n}\n```\n\nThis second method for computing $\\textrm{Fib}(n)$\nis a linear iteration.\n\nThe difference in number of steps required by the two\nmethods one linear in $n$ , one growing as\nfast as $\\textrm{Fib}(n)$ itself is\nenormous, even for small inputs.\n\nOne should not conclude from this that tree-recursive processes are useless.\n\nWhen we consider processes that operate on hierarchically structured data\nrather than numbers, we will find that tree recursion is a natural and\npowerful tool.\n\nBut\neven in numerical operations, tree-recursive processes can be useful in\nhelping us to understand and design programs.", + "token_count": 301, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1530,8 +1550,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Tree_Recursion_2" }, { - "content": "The difference in number of steps required by the two\nmethods one linear in $n$ , one growing as\nfast as $\\textrm{Fib}(n)$ itself is\nenormous, even for small inputs.\n\nOne should not conclude from this that tree-recursive processes are useless.\n\nWhen we consider processes that operate on hierarchically structured data\nrather than numbers, we will find that tree recursion is a natural and\npowerful tool.\nfunction\nis much less efficient than the second one, it is more straightforward,\nbeing little more than a translation into\nJavaScript\nof the definition of the Fibonacci sequence.\n\nTo formulate the iterative\nalgorithm required noticing that the computation could be recast as an\niteration with three state variables.\n\nIt takes only a bit of cleverness to come up with the iterative Fibonacci\nalgorithm.\n\nIn contrast, consider the following problem:\nHow many different ways can we make change of\n1.00 (100 cents),\ngiven half-dollars, quarters, dimes, nickels, and pennies\n(50 cents, 25 cents, 10 cents, 5 cents, and 1 cent, respectively)?\n\nMore generally, can\nwe write a\nfunction\nto compute the number of ways to change any given amount of money?\n\nThis problem has a simple solution as a recursive\nfunction.\n\nSuppose we think of the types of coins available as arranged in some order.\n\nThen the following relation holds:\nThe number of ways to change amount $a$ using\n$n$ kinds of coins equals\n-\n-\nthe number of ways to change amount $a$\nusing all but the first kind of coin, plus\n-\n-\nthe number of ways to change amount $a-d$\nusing all $n$ kinds of coins, where\n$d$ is the denomination of the first kind\nof coin.", - "token_count": 274, + "content": "But\neven in numerical operations, tree-recursive processes can be useful in\nhelping us to understand and design programs.\n\nTo formulate the iterative\nalgorithm required noticing that the computation could be recast as an\niteration with three state variables.\n\nIt takes only a bit of cleverness to come up with the iterative Fibonacci\nalgorithm.\n\nIn contrast, consider the following problem:\nHow many different ways can we make change of\n1.00 (100 cents),\ngiven half-dollars, quarters, dimes, nickels, and pennies\n(50 cents, 25 cents, 10 cents, 5 cents, and 1 cent, respectively)?\n\nMore generally, can\nwe write a\nfunction\nto compute the number of ways to change any given amount of money?\n\nThis problem has a simple solution as a recursive\nfunction.\n\nSuppose we think of the types of coins available as arranged in some order.\n\nThen the following relation holds:\nThe number of ways to change amount $a$ using\n$n$ kinds of coins equals\n-\n-\nthe number of ways to change amount $a$\nusing all but the first kind of coin, plus\n-\n-\nthe number of ways to change amount $a-d$\nusing all $n$ kinds of coins, where\n$d$ is the denomination of the first kind\nof coin.\n\nTo see why this is true, observe that the ways to make change can be divided\ninto two groups: those that do not use any of the first kind of coin, and\nthose that do.\n\nTherefore, the total number of ways to make change for some\namount is equal to the number of ways to make change for the amount without\nusing any of the first kind of coin, plus the number of ways to make change\nassuming that we do use the first kind of coin.", + "token_count": 286, "has_code": false, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", @@ -1540,9 +1560,9 @@ "chunk_id": "Building_Abstractions_with_Functions_Tree_Recursion_3" }, { - "content": "Then the following relation holds:\nThe number of ways to change amount $a$ using\n$n$ kinds of coins equals\n-\n-\nthe number of ways to change amount $a$\nusing all but the first kind of coin, plus\n-\n-\nthe number of ways to change amount $a-d$\nusing all $n$ kinds of coins, where\n$d$ is the denomination of the first kind\nof coin.\n\nTo see why this is true, observe that the ways to make change can be divided\ninto two groups: those that do not use any of the first kind of coin, and\nthose that do.\n\nTherefore, the total number of ways to make change for some\namount is equal to the number of ways to make change for the amount without\nusing any of the first kind of coin, plus the number of ways to make change\nassuming that we do use the first kind of coin.\n\nBut the latter number is\nequal to the number of ways to make change for the amount that remains after\nusing a coin of the first kind.\n\nThus, we can recursively reduce the problem of changing a given amount to\nproblems of changing smaller amounts or using fewer kinds of coins.\n\nConsider\nthis reduction rule carefully, and convince yourself that we can use it to\ndescribe an algorithm if we specify the following degenerate\ncases:\n-\n-\nIf $a$ is exactly 0, we should count that\nas 1 way to make change.\n-\n-\nIf $a$ is less than 0, we should count\nthat as 0 ways to make change.\n-\n- If $n$ is 0, we should count that\nas 0 ways to make change.\n\nWe can easily translate this description into a recursive\nfunction:", - "token_count": 288, - "has_code": false, + "content": "Therefore, the total number of ways to make change for some\namount is equal to the number of ways to make change for the amount without\nusing any of the first kind of coin, plus the number of ways to make change\nassuming that we do use the first kind of coin.\n\nThus, we can recursively reduce the problem of changing a given amount to\nproblems of changing smaller amounts or using fewer kinds of coins.\n\nConsider\nthis reduction rule carefully, and convince yourself that we can use it to\ndescribe an algorithm if we specify the following degenerate\ncases:\n-\n-\nIf $a$ is exactly 0, we should count that\nas 1 way to make change.\n-\n-\nIf $a$ is less than 0, we should count\nthat as 0 ways to make change.\n-\n- If $n$ is 0, we should count that\nas 0 ways to make change.\n\nWe can easily translate this description into a recursive\nfunction:\n\n```javascript\ncount_change_definition\n count_change_example\n\nfunction count_change(amount) {\n return cc(amount, 5);\n}\n\nfunction cc(amount, kinds_of_coins) {\n return amount === 0\n ? 1\n : amount < 0 || kinds_of_coins === 0\n ? 0\n : cc(amount, kinds_of_coins - 1)\n +\n cc(amount - first_denomination(kinds_of_coins),\n kinds_of_coins);\n}\n\nfunction first_denomination(kinds_of_coins) {\n return kinds_of_coins === 1 ? 1\n : kinds_of_coins === 2 ? 5\n : kinds_of_coins === 3 ? 10\n : kinds_of_coins === 4 ? 25\n : kinds_of_coins === 5 ? 50\n : 0;\n}\n```\n\n(The\nfirst_denomination function\ntakes as input the number of kinds of coins available and returns the\ndenomination of the first kind.\n\nHere we are thinking of the coins as\narranged in order from largest to smallest, but any order would do as well.)\nWe can now answer our original question about changing a dollar:\n\n```javascript\ncount_change_example\n count_change_definition\n 292\n\ncount_change(100);\n\n292\n```", + "token_count": 300, + "has_code": true, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", "subsection": "Tree Recursion", @@ -1550,9 +1570,9 @@ "chunk_id": "Building_Abstractions_with_Functions_Tree_Recursion_4" }, { - "content": "We can easily translate this description into a recursive\nfunction:\n\n```javascript\ncount_change_definition\n count_change_example\n\nfunction count_change(amount) {\n return cc(amount, 5);\n}\n\nfunction cc(amount, kinds_of_coins) {\n return amount === 0\n ? 1\n : amount < 0 || kinds_of_coins === 0\n ? 0\n : cc(amount, kinds_of_coins - 1)\n +\n cc(amount - first_denomination(kinds_of_coins),\n kinds_of_coins);\n}\n\nfunction first_denomination(kinds_of_coins) {\n return kinds_of_coins === 1 ? 1\n : kinds_of_coins === 2 ? 5\n : kinds_of_coins === 3 ? 10\n : kinds_of_coins === 4 ? 25\n : kinds_of_coins === 5 ? 50\n : 0;\n}\n```\n\n(The\nfirst_denomination function\ntakes as input the number of kinds of coins available and returns the\ndenomination of the first kind.\n\nHere we are thinking of the coins as\narranged in order from largest to smallest, but any order would do as well.)\nWe can now answer our original question about changing a dollar:\n\n```javascript\ncount_change_example\n count_change_definition\n 292\n\ncount_change(100);\n```\n\nThe function count_change\ngenerates a tree-recursive process with redundancies similar to those in\nour first implementation of\nOn the other hand, it is not\nobvious how to design a better algorithm for computing the result, and we\nleave this problem as a challenge.\n\nThe observation that a\nsmart compiler that could transform\ntree-recursive\nfunctions\ninto more efficient\nfunctions\nthat compute the same result.", - "token_count": 211, - "has_code": true, + "content": "Here we are thinking of the coins as\narranged in order from largest to smallest, but any order would do as well.)\nWe can now answer our original question about changing a dollar:\n\nOn the other hand, it is not\nobvious how to design a better algorithm for computing the result, and we\nleave this problem as a challenge.\n\nThe observation that a\ntree-recursive process may be highly inefficient but often easy to specify\nand understand has led people to propose that one could get the best of both\nworlds by designing a smart compiler that could transform\ntree-recursive\nfunctions\ninto more efficient\nfunctions\nthat compute the same result.", + "token_count": 109, + "has_code": false, "chapter": "Building Abstractions with Functions", "section": "Functions and the Processes They Generate", "subsection": "Tree Recursion", @@ -1560,8 +1580,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Tree_Recursion_5" }, { - "content": "A powerful programming language is more than just a means for\ninstructing a computer to perform tasks.\n\nThe language also serves as\na framework within which we organize our ideas about processes.\n\nThus,\nwhen we describe a language, we should pay particular attention to the\nmeans that the language provides for combining simple ideas to form\nmore complex ideas.\n\nEvery powerful language has three mechanisms for\naccomplishing this:\n-\n-\nprimitive expressions ,\ncerned with,\n-\n-\nmeans of combination , by\n-\n-\nmeans of abstraction ,\n\nIn programming, we deal with two kinds of elements:\nand\nstuff that we want to manipulate, and\nfunctions\nare descriptions of the rules for manipulating the data.\n\nThus, any powerful programming language should be able to describe\nprimitive data and primitive\nfunctions\nand should have methods for\ncombining and abstracting\nfunctions\nand data.\n\nIn this chapter we will deal only with simple functions. functions to manipulate compound data as well.", - "token_count": 158, + "content": "A powerful programming language is more than just a means for\ninstructing a computer to perform tasks.\n\nThe language also serves as\na framework within which we organize our ideas about processes.\n\nThus,\nwhen we describe a language, we should pay particular attention to the\nmeans that the language provides for combining simple ideas to form\nmore complex ideas.\n\nEvery powerful language has three mechanisms for\naccomplishing this:\n-\n-\nprimitive expressions ,\nwhich represent the simplest\nentities the language is con cerned with,\n-\n-\nmeans of combination , by\nwhich compound elements are built from simpler ones, and\n-\n-\nmeans of abstraction ,\nby which compound elements can be named and manipulated as units.\n\nIn programming, we deal with two kinds of elements:\nfunctions\nand\ndata.\n\n(Later we will discover that they are really not so distinct.)\nInformally, data is stuff that we want to manipulate, and\nfunctions\nare descriptions of the rules for manipulating the data.\n\nThus, any powerful programming language should be able to describe\nprimitive data and primitive\nfunctions\nand should have methods for\ncombining and abstracting\nfunctions\nand data.\n\nIn this chapter we will deal only with simple\nnumerical data so that\nwe can focus on the rules for building\nfunctions.\n\nIn later chapters we will see that\nthese same rules allow us to build\nfunctions\nto manipulate compound data as well.", + "token_count": 229, "has_code": false, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", @@ -1570,8 +1590,8 @@ "chunk_id": "Building_Abstractions_with_Functions_The_Elements_of_Programming_1" }, { - "content": "We have identified in\nJavaScript\nsome of the elements that must appear in any powerful programming language:\n-\n-\nNumbers and arithmetic operations are primitive data and\nfunctions.\n-\n-\nNesting of combinations provides a means of combining operations.\n-\n-\nConstant declarations that associate names with values provide a\nlimited means of abstraction.\n\nNow we will learn about\nfunction declarations,\na much more powerful abstraction technique by which a compound\noperation can be given a name and then referred to as a unit.\n\nWe begin by examining how to express the idea of\nsquaring.\n\nWe might say,\nTo square something, take it times itself.\n\n```javascript\nsquare_definition\n square_example\n\nfunction square(x) {\n return x * x;\n}\n```\n\n```javascript\nsquare_example\n\nsquare(14);\n```\n\nWe can understand this in the following way:\n\\begin{flushleft}\\normalcodesize\n\\begin{tabular}{@{}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c}\n\\tt\\textbf{function} & \\tt square( & \\tt x & \\tt ) \\verb+{+ & \\tt\\textbf{return} & \\tt x & \\tt * & \\tt x & \\tt; \\verb+}+ \\\\\n$\\Big\\uparrow$ & $\\Big\\uparrow$ & $\\Big\\uparrow$ & & & $\\Big\\uparrow$ & $\\Big\\uparrow$ & $\\Big\\uparrow$ \\\\[4pt]\n\\normalsize To & \\normalsize square & \\normalsize something, & & \\normalsize take &\\normalsize it & \\normalsize times & \\normalsize itself. \\\\\n\\end{tabular}\n\\end{flushleft}\nWe have here a\ncompound function,\nwhich has been given the name\nfunction\nrepresents the operation of multiplying something by itself.\n\nThe thing to\nbe multiplied is given a local name,\nEvaluating the\ndeclaration\ncreates this compound\nfunction\nand associates it with the name\n\nThe simplest form of a function declaration is\n\n```javascript\nfunction name(parameters) { return expression; }\n```\n\nThe name is a symbol to be associated with the function definition in the environment. parameters are the names used within the body of the\n\nfunction to refer to the corresponding arguments of the function.", - "token_count": 289, + "content": "We have identified in\nJavaScript\nsome of the elements that must appear in any powerful programming language:\n-\n-\nNumbers and arithmetic operations are primitive data and\nfunctions.\n-\n-\nNesting of combinations provides a means of combining operations.\n-\n-\nConstant declarations that associate names with values provide a\nlimited means of abstraction.\n\nNow we will learn about\nfunction declarations,\na much more powerful abstraction technique by which a compound\noperation can be given a name and then referred to as a unit.\n\nWe begin by examining how to express the idea of\nsquaring.\n\nWe might say,\nTo square something, take it times itself.\n\nThis is expressed in our language as\n\n```javascript\nsquare_definition\n square_example\n\nfunction square(x) {\n return x * x;\n}\n```\n\n```javascript\nsquare_example\n\nsquare(14);\n```\n\nWe can understand this in the following way:\n\\begin{flushleft}\\normalcodesize\n\\begin{tabular}{@{}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c}\n\\tt\\textbf{function} & \\tt square( & \\tt x & \\tt ) \\verb+{+ & \\tt\\textbf{return} & \\tt x & \\tt * & \\tt x & \\tt; \\verb+}+ \\\\\n$\\Big\\uparrow$ & $\\Big\\uparrow$ & $\\Big\\uparrow$ & & & $\\Big\\uparrow$ & $\\Big\\uparrow$ & $\\Big\\uparrow$ \\\\[4pt]\n\\normalsize To & \\normalsize square & \\normalsize something, & & \\normalsize take &\\normalsize it & \\normalsize times & \\normalsize itself. \\\\\n\\end{tabular}\n\\end{flushleft}\nWe have here a\ncompound function,\nwhich has been given the name.\n\nThe\nfunction\nrepresents the operation of multiplying something by itself.\n\nThe thing to\nbe multiplied is given a local name, ,\nwhich plays the same role that a pronoun plays in natural language.\n\nEvaluating the\ndeclaration\ncreates this compound\nfunction\nand associates it with the name.\n\nThe simplest form of a function declaration is\n\n```javascript\nfunction name(parameters) { return expression; }\n```\n\nThe\nname\nis a symbol to be associated with the\nfunction\ndefinition in the environment.", + "token_count": 290, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", @@ -1580,8 +1600,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Compound_Functions_1" }, { - "content": "function to refer to the corresponding arguments of the function.\n\n```javascript\nThe parameters\n\tare grouped within\n\tbody of a function declaration is a single\n\treturn statement,return\n\tfollowed by the return expression\n\tthat will yield the value of the function application, when the\n\n\tparameters are replaced by the actual arguments to which the function\n\tis applied. Like constant declarations and expression statements,\n\treturn statements\n```\n\n```javascript\nHaving declared square,\n\twe can now use it in a\n\tfunction application expression, which we turn into a statement\n\tusing a semicolon:\n```\n\n```javascript\nsquare_definition\n\nsquare(21);\n```\n\n```javascript\nafter operator\n\tcombinationsthe second kind of combination of\n\texpressions into larger expressions that we encounter.\n\tThe general form of a function application is\n\nfunction-expression(argument-expressions)\n\n\twhere the\n\tfunction-expression\n\tof the application specifies\n\tthe function to be applied to the comma-separated\n argument-expressions.\n\tTo evaluate a function application, the interpreter follows\n\t.\n\n\t To evaluate a function application, do the following:\n\n\t Evaluate the subexpressions of the application, namely\n\t the function expression and the argument expressions.\n\n\t Apply the function that is the value of the function expression\n to the values of the argument expressions.\n```\n\n```javascript\nsquare_definition\n\nsquare(2 + 5);\n```\n\n```javascript\nHere, the argument expression is itself a compound expression,\n\tthe operator combination 2 + 5.\n```\n\n```javascript\nsquare_square\n 81\n square_definition\n\nsquare(square(3));\n```\n\nOf course function application expressions can also serve as argument expressions.\n\nWe can also use\nfunctions.\n\nFor example, $x^2 +y^2$ can be expressed as\n\n```javascript\nsquare(x) + square(y)\n```\n\nWe can easily declare a\n\n```javascript\nfunction\n\tsum_of_squares\n```\n\nthat, given any two numbers as arguments, produces the sum of their squares:\n\n```javascript\nsum_of_squares\n 25\n sum_of_squares_example\n square_definition\n\nfunction sum_of_squares(x, y) {\n return square(x) + square(y);\n}\n```\n\n```javascript\nsum_of_squares_example\n 25\n sum_of_squares\n\nsum_of_squares(3, 4);\n```\n\nNow we can use sum_of_squares as a building block in constructing further functions:\n\n```javascript\nf\n f_example\n 136\n sum_of_squares\n\nfunction f(a) {\n return sum_of_squares(a + 1, a * 2);\n}\n```\n\n```javascript\nf_example\n f\n\nf(5);\n```", - "token_count": 320, + "content": "The\nname\nis a symbol to be associated with the\nfunction\ndefinition in the environment.\n\nThe parameters are grouped within parentheses and separated by commas, as they will be in an application of the function being declared.\n\nIn the simplest form, the body of a function declaration is a single return statement, which consists of the keyword return followed by the return expression that will yield the value of the function application, when the parameters are replaced by the actual arguments to which the function is applied.\n\nLike constant declarations and Statements, return statements end with a semicolon.\n\nHaving declared square, we can now use it in a function application expression, which we turn into a statement using a semicolon:\n\n```javascript\nsquare_definition\n\nsquare(21);\n\n441\n```\n\nFunction applications areafter operator combinationsthe second kind of combination of expressions into larger expressions that we encounter.\n\nThe general form of a function application is function-expression(argument-expressions) where the function-expression of the application specifies the function to be applied to the comma-separated argument-expressions.\n\nTo evaluate a function application, the interpreter follows a procedure quite similar to the procedure for operator combinations described in section.\n\nTo evaluate a function application, do the following: Evaluate the subexpressions of the application, namely the function expression and the argument expressions.\n\nApply the function that is the value of the function expression to the values of the argument expressions.\n\n```javascript\nsquare_definition\n\nsquare(2 + 5);\n\n49\n```\n\nHere, the argument expression is itself a compound expression, the operator combination 2 + 5.\n\n```javascript\nsquare_square\n 81\n square_definition\n\nsquare(square(3));\n\n81\n```\n\nOf course function application expressions can also serve as argument expressions.\n\nWe can also use\nas a building block in defining other\nfunctions.\n\nFor example, $x^2 +y^2$ can be expressed as\n\n```javascript\nsquare(x) + square(y)\n```", + "token_count": 293, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", @@ -1590,8 +1610,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Compound_Functions_2" }, { - "content": "Now we can use sum_of_squares as a building block in constructing further functions:\n\n```javascript\nIn addition to compound functions, any JavaScript environment provides\n\tmath_log,\n\twhich computes the natural logarithm of its argument.math_log(1) results in the\tnumber 0.\n\tIndeed, one could not tell by looking at the definition of\n\tsum_of_squares given above whether\n\tsquare was built into the\n\tinterpreter, loaded from a library, or defined as a compound function.\n```", - "token_count": 69, + "content": "For example, $x^2 +y^2$ can be expressed as\n\n```javascript\nsum_of_squares\n 25\n sum_of_squares_example\n square_definition\n\nfunction sum_of_squares(x, y) {\n return square(x) + square(y);\n}\n```\n\n```javascript\nsum_of_squares_example\n 25\n sum_of_squares\n\nsum_of_squares(3, 4);\n\n25\n```\n\nNow we can use sum_of_squares as a building block in constructing further functions:\n\n```javascript\nf\n f_example\n 136\n sum_of_squares\n\nfunction f(a) {\n return sum_of_squares(a + 1, a * 2);\n}\n```\n\n```javascript\nf_example\n f\n\nf(5);\n\n136\n```\n\nIn addition to compound functions, any JavaScript environment provides primitive functions that are built into the interpreter or loaded from libraries.\n\nBesides the primitive functions provided by the operators, the JavaScript environment used in this book includes additional primitive functions such as the function math_log, which computes the natural logarithm of its argument.\n\nThese additional primitive functions are used in exactly the same way as compound functions; evaluating the application math_log(1) results in the number 0.\n\nIndeed, one could not tell by looking at the definition of sum_of_squares given above whether square was built into the interpreter, loaded from a library, or defined as a compound function.", + "token_count": 174, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", @@ -1600,9 +1620,9 @@ "chunk_id": "Building_Abstractions_with_Functions_Compound_Functions_3" }, { - "content": "The expressive power of the class of functions that we can define at this point is very limited, because we have no way to make\n\ntests and to perform different operations depending on the result of a test.\n\n```javascript\n\\[\\begin{array}{lll}\n |x| & = & \\left\\{ \\begin{array}{rl}\n x & \\mbox{if $x \\geq 0$} \\\\\n -x & \\mbox{otherwise}\n \\end{array}\n \\right.\n \\end{array}\\]\n\n\tcase analysis and can be written\n\tin JavaScript using a conditional expression as\n\n\t abs_definition\n abs_example\n\nfunction abs(x) {\n return x >= 0 ? x : - x;\n}\n\n abs_example\n\t abs_definition\n\t 5\n\nabs(-5);\n\n\twhich could be expressed in English as If $x$ is\n\tgreater than or equal to zero, return$x$; otherwise\n\treturn $- x$.\n The general form of a conditional expression is\n\npredicate ? consequent-expression : alternative-expression\n\n Conditional\n\tpredicatethat is,\n an expression whose value is either\n\ttrue or false, two distinguished\n\tboolean values in JavaScript.\n\tThe primitive boolean expressions\n\ttrue and\n\tfalse trivially evaluate\n\tto the boolean values true and false, respectively.\n\tThe predicate\n is followed by a question mark, the\n consequent-expression,\n a colon, and finally the\n alternative-expression.\n```\n\nTo\npredicate\nof the expression.\n\nIf the\npredicate\nevaluates to true, the interpreter evaluates the\nconsequent-expression and returns its value as the value of the conditional.\n\nIf the predicate\nevaluates to false, it evaluates the\nalternative-expression and returns its value as the value of the\nconditional.\n\nThe word\npredicate is used for operators and functions that\nreturn true or false, as well as for expressions that\nevaluate to true or false.\n\nThe absolute-value function\nabs makes use of the\n>= ,\nan operator that takes two numbers as arguments and tests whether the\nfirst number is greater than or equal to the second number, returning\ntrue or false accordingly.", - "token_count": 286, - "has_code": true, + "content": "The expressive power of the class of\nfunctions\nthat we can define at this point is very limited, because we have no way to\nmake tests and to perform different operations depending on the result of a\ntest.\n\nFor instance, we cannot declare a function that computes the absolute value of a number by testing whether the number is nonnegative and taking different actions in each case according to the rule \\[\\begin{array}{lll} |x| & = & \\left\\{ \\begin{array}{rl} x & \\mbox{if $x \\geq 0$} \\\\ -x & \\mbox{otherwise} \\end{array} \\right. \\end{array}\\] This construct is a case analysis and can be written in JavaScript using a conditional expression as abs_definition abs_example function abs(x) { return x >= 0 ? x : - x; } abs_example abs_definition 5 abs(-5); which could be expressed in English as If $x$ is greater than or equal to zero, return$x$; otherwise return $- x$.\n\nThe general form of a conditional expression is predicate ? consequent-expression : alternative-expression Conditional expressions begin with a predicatethat is, an expression whose value is either true or false, two distinguished boolean values in JavaScript.\n\nThe primitive boolean expressions true and false trivially evaluate to the boolean values true and false, respectively.\n\nThe predicate is followed by a question mark, the consequent-expression, a colon, and finally the alternative-expression.\n\nTo\nevaluate a conditional expression,\nthe interpreter starts by evaluating the\npredicate\nof the expression.\n\nIf the\npredicate\nevaluates to true, the interpreter evaluates the\nconsequent-expression and returns its value as the value of the conditional.\n\nIf the predicate\nevaluates to false, it evaluates the\nalternative-expression and returns its value as the value of the\nconditional.\n\nThe word\npredicate is used for operators and functions that\nreturn true or false, as well as for expressions that\nevaluate to true or false.", + "token_count": 297, + "has_code": false, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", "subsection": "Conditional Expressions and Predicates", @@ -1610,7 +1630,7 @@ "chunk_id": "Building_Abstractions_with_Functions_Conditional_Expressions_and_Predicates_1" }, { - "content": "The absolute-value function\nabs makes use of the\n>= ,\nan operator that takes two numbers as arguments and tests whether the\nfirst number is greater than or equal to the second number, returning\ntrue or false accordingly.\n\nIf we prefer to handle the zero case separately, we can specify the function that computes the absolute value of a number by writing \\[\\begin{array}{lll}\n\n|x| &=& \\left\\{ \\begin{array}{rl} x & \\mbox{if $x > 0$} \\\\ 0 & \\mbox{if $x = 0$} \\\\ -x & \\mbox{otherwise} \\end{array} \\right. \\end{array}\\] In\n\nJavaScript, we express a case analysis with multiple cases by nesting conditional expressions as alternative expressions inside other conditional expressions:\n\n```javascript\nabs_example\n\nfunction abs(x) {\n return x > 0\n ? x\n : x === 0\n ? 0\n : - x;\n}\n```\n\nParentheses are not needed around the alternative expression\nx === 0 ?\n\n0 : - x , because\nthe conditional-expression syntactic form\n? s\nand : s under the first predicate\nof the case analysis.\n\nThe general form of a\n\n```javascript\np$_1$\n? e$_1$\n: p$_2$\n? e$_2$\n$\\vdots$\n: p$_n$\n? e$_n$\n: final-alternative-expression\n```\n\nWe call a predicate $p_i$\ntogether with its consequent expression\n$e_i$\na\nclause.\n\nA case analysis\ncan be seen as a sequence of clauses, followed by a final\nalternative expression.\np $_1$.\n\nIf its value is false, then p $_2$\nis evaluated.\n\nIf p $_2$ s\nvalue is also false, then p $_3$\nis evaluated.\n\nThis process continues until a predicate is\nfound whose value is true, in which case the interpreter returns the\nvalue of the corresponding\ne\nof the clause\nas the value of the case analysis.\n\nIf none of the\np s\nis found to be true, the value of the case analysis\nis the value of the final alternative expression.", + "content": "The word\npredicate is used for operators and functions that\nreturn true or false, as well as for expressions that\nevaluate to true or false.\n\nIf we prefer to handle the zero case separately, we can specify the function that computes the absolute value of a number by writing \\[\\begin{array}{lll}\n\n|x| &=& \\left\\{ \\begin{array}{rl} x & \\mbox{if $x > 0$} \\\\ 0 & \\mbox{if $x = 0$} \\\\ -x & \\mbox{otherwise} \\end{array} \\right. \\end{array}\\] In\n\nJavaScript, we express a case analysis with multiple cases by nesting conditional expressions as alternative expressions inside other conditional expressions:\n\n```javascript\nabs_example\n\nfunction abs(x) {\n return x > 0\n ? x\n : x === 0\n ? 0\n : - x;\n}\n```\n\nParentheses are not needed around the alternative expression\nx === 0 ?\n\n0 : - x , because\nthe conditional-expression syntactic form\nis right-associative.\n\nThe interpreter ignores spaces and line breaks, here inserted for readability\nto align the\n? s\nand : s under the first predicate\nof the case analysis.\n\nThe general form of a\ncase analysis is\n\n```javascript\np$_1$\n? e$_1$\n: p$_2$\n? e$_2$\n$\\vdots$\n: p$_n$\n? e$_n$\n: final-alternative-expression\n```\n\nWe call a predicate $p_i$\ntogether with its consequent expression\n$e_i$\na\nclause.\n\nA case analysis\ncan be seen as a sequence of clauses, followed by a final\nalternative expression.\n\nAccording to the evaluation of conditional expressions,\na case analysis is evaluated by first evaluating\nthe predicate p $_1$.\n\nIf its value is false, then p $_2$\nis evaluated.\n\nIf p $_2$ s\nvalue is also false, then p $_3$\nis evaluated.\n\nThis process continues until a predicate is\nfound whose value is true, in which case the interpreter returns the\nvalue of the corresponding\nconsequent expression\ne\nof the clause\nas the value of the case analysis.", "token_count": 300, "has_code": true, "chapter": "Building Abstractions with Functions", @@ -1620,9 +1640,9 @@ "chunk_id": "Building_Abstractions_with_Functions_Conditional_Expressions_and_Predicates_2" }, { - "content": "If none of the\np s\nis found to be true, the value of the case analysis\nis the value of the final alternative expression.\n\nIn addition to primitive predicates such as\n>= ,\n> ,\n< ,\n<= ,\n=== , and\n!== that are applied to\nnumbers,\n-\n-\nexpression $_1$ &&\nexpression $_2$\nThis operation expresses\nlogical conjunction , meaning roughly\nthe same as the English word and.\n\nWe assume\nexpression $_1$ ?\nexpression $_2$ :\nfalse.\n-\n-\nexpression $_1$\n||\nexpression $_2$\nThis operation expresses\nlogical disjunction , meaning roughly\nthe same as the English word or.\n\nWe assume this syntactic form to be syntactic sugar for\nexpression $_1$ ?\ntrue :\nexpression $_2$.\n-\n-\n!\nexpression\nThis operation expresses\nlogical negation , meaning\nroughly the same as the English word not.\n\nThe value of the expression is true when\nexpression\nevaluates to false, and false when\nexpression\nevaluates to true.\n\nNotice that && and\n|| are syntactic forms,\nnot operators;\n! , on the other hand,\nfollows the evaluation rule of section.\n\nIt is a unary operator, which means that it takes only\none argument, whereas the arithmetic operators and primitive predicates\ndiscussed so far\nare binary , taking two arguments.\n\nThe operator\n! precedes its argument;\nwe call it a\nprefix operator.\n\nAnother prefix operator is\nthe numeric negation operator, an example of\nwhich is the expression - x\nin the abs functions above.\n\nAs an example of how these predicates are used, the condition that a number $x$ be in the range $5 < x < 10$ may\n\nbe expressed as\n\n```javascript\nx > 5 && x < 10\n```", - "token_count": 279, - "has_code": true, + "content": "This process continues until a predicate is\nfound whose value is true, in which case the interpreter returns the\nvalue of the corresponding\nconsequent expression\ne\nof the clause\nas the value of the case analysis.\n\nIn addition to primitive predicates such as\n>= ,\n> ,\n< ,\n<= ,\n=== , and\n!== that are applied to\nnumbers,\nthere are logical composition operations, which enable us to construct\ncompound predicates.\n\nThe three most frequently used are these:\n-\n-\nexpression $_1$ &&\nexpression $_2$\nThis operation expresses\nlogical conjunction , meaning roughly\nthe same as the English word and.\n\nWe assume this syntactic form to be syntactic\nsugar for\nexpression $_1$ ?\nexpression $_2$ :\nfalse.\n-\n-\nexpression $_1$\n||\nexpression $_2$\nThis operation expresses\nlogical disjunction , meaning roughly\nthe same as the English word or.\n\nWe assume this syntactic form to be syntactic sugar for\nexpression $_1$ ?\ntrue :\nexpression $_2$.\n-\n-\n!\nexpression\nThis operation expresses\nlogical negation , meaning\nroughly the same as the English word not.\n\nThe value of the expression is true when\nexpression\nevaluates to false, and false when\nexpression\nevaluates to true.\n\nNotice that && and\n|| are syntactic forms,\nnot operators;\ntheir right-hand\nexpression is not always evaluated.\n\nThe operator\n! , on the other hand,\nfollows the evaluation rule of section.\n\nIt is a unary operator, which means that it takes only\none argument, whereas the arithmetic operators and primitive predicates\ndiscussed so far\nare binary , taking two arguments.\n\nThe operator\n! precedes its argument;\nwe call it a\nprefix operator.\n\nAnother prefix operator is\nthe numeric negation operator, an example of\nwhich is the expression - x\nin the abs functions above.", + "token_count": 289, + "has_code": false, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", "subsection": "Conditional Expressions and Predicates", @@ -1630,8 +1650,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Conditional_Expressions_and_Predicates_3" }, { - "content": "be expressed as\n\nThe syntactic form && has lower precedence than the comparison operators > and < , and the conditional-expression syntactic form $\\cdots$ ? $\\cdots$ : $\\cdots$\n\nhas lower precedence than any other operator we have encountered so far, a property we used in the abs functions above.\n\nAs another example, we can declare a predicate to test whether one number is greater than or equal to another as\n\n```javascript\ngeq_example\n\nfunction greater_or_equal(x, y) {\n return x > y || x === y;\n}\n```\n\nor alternatively as\n\n```javascript\ngeq_example\n\nfunction greater_or_equal(x, y) {\n return ! (x < y);\n}\n```\n\n```javascript\ngeq_example\n\ngreater_or_equal(7, 4);\n```\n\n```javascript\nThe function greater_or_equal,\n\twhen applied to two numbers, behaves the same as the operator\n\t>=. Unary operators have\n```", - "token_count": 127, + "content": "Another prefix operator is\nthe numeric negation operator, an example of\nwhich is the expression - x\nin the abs functions above.\n\nbe expressed as\n\n```javascript\nx > 5 && x < 10\n```\n\nThe syntactic form && has lower precedence than the comparison operators > and < , and the conditional-expression syntactic form $\\cdots$ ? $\\cdots$ : $\\cdots$\n\nhas lower precedence than any other operator we have encountered so far, a property we used in the abs functions above.\n\nAs another example, we can declare a predicate to test whether one number is greater than or equal to another as\n\n```javascript\ngeq_example\n\nfunction greater_or_equal(x, y) {\n return x > y || x === y;\n}\n```\n\nor alternatively as\n\n```javascript\ngeq_example\n\nfunction greater_or_equal(x, y) {\n return ! (x < y);\n}\n```\n\n```javascript\ngeq_example\n\ngreater_or_equal(7, 4);\n```\n\nThe function greater_or_equal, when applied to two numbers, behaves the same as the operator >=.\n\nUnary operators have higher precedence than binary operators, which makes the parentheses in this example necessary.", + "token_count": 169, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", @@ -1640,8 +1660,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Conditional_Expressions_and_Predicates_4" }, { - "content": "One of our goals in this chapter is to isolate issues about thinking\nprocedurally.\n\nAs a case in point, let us consider that, in evaluating\noperator\ncombinations, the interpreter is itself following a procedure.\n-\n-\nTo evaluate\nan operator combination,\ndo the following:\n-\n- Evaluate the\noperand expressions\nof the combination.\n-\n-\n\n```javascript\nApply the function that is denoted by\n the operator to the arguments that are the values of\n the operands.\n```\n\nrecursive in nature; that is, it includes, as one of its steps, the need to invoke the rule itself.\n\nNotice how succinctly the idea of recursion can be used to express\n\n```javascript\n(2 + 4 * 6) * (3 + 12);\n```\n\nrequires that the evaluation rule be applied to four different\ncombinations.\n\nWe can obtain a picture of this process by\nrepresenting the combination in the form of a\nfigure.\n\nEach combination is represented by a\npercolate values\nupward form of the evaluation rule is an example of a general kind\nof process known as\ntree accumulation.\n\nTree representation, showing the value of each subexpression.\n\nNext, observe that the repeated application of the first step brings\nus to the point where we need to evaluate, not combinations, but\nprimitive expressions such as\nnumerals or names.\n\nWe take care of the primitive cases\n-\n-\nthe values of numerals are the numbers that they name,\nand\n-\n-\nthe values of\nnames are the objects associated\nwith those names in the environment.\n\nThe key point to\nnotice is the role of the\nnames\nin expressions.", - "token_count": 261, + "content": "One of our goals in this chapter is to isolate issues about thinking\nprocedurally.\n\nAs a case in point, let us consider that, in evaluating\noperator\ncombinations, the interpreter is itself following a procedure.\n-\n-\nTo evaluate\nan operator combination,\ndo the following:\n-\n- Evaluate the\noperand expressions\nof the combination.\n-\n-\nApply the function that is denoted by the operator to the arguments that are the values of the operands.\n\nEven this simple rule illustrates some important points about\nprocesses in general.\n\nFirst, observe that the first step dictates\nthat in order to accomplish the evaluation process for a\ncombination we must first perform the evaluation process on each\noperand of the combination.\n\nThus, the evaluation rule is\nrecursive in nature;\nthat is, it includes, as one of its steps, the need to invoke the rule\nitself.\n\nNotice how succinctly the idea of recursion can be used to express\nwhat, in the case of a deeply nested combination, would otherwise be\nviewed as a rather complicated process.\n\nFor example, evaluating\n\n```javascript\n(2 + 4 * 6) * (3 + 12);\n```\n\nrequires that the evaluation rule be applied to four different\ncombinations.\n\nWe can obtain a picture of this process by\nrepresenting the combination in the form of a\ntree, as shown in\nfigure.\n\nEach combination is represented by a\nnode with\nbranches corresponding to the operator and the\noperands of the combination stemming from it.\n\nThe\nterminal nodes (that is, nodes with\nno branches stemming from them) represent either operators or numbers.\n\nViewing evaluation in terms of the tree, we can imagine that the\nvalues of the operands percolate upward, starting from the terminal\nnodes and then combining at higher and higher levels.", + "token_count": 288, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", @@ -1650,9 +1670,9 @@ "chunk_id": "Building_Abstractions_with_Functions_Evaluating_Operator_Combinations_1" }, { - "content": "The key point to\nnotice is the role of the\nnames\nin expressions.\n\nIn an interactive language such as\nJavaScript,\nit is meaningless to speak of the value of an expression such as\nx + 1\nwithout specifying any information about the environment\nthat would provide a meaning for the\nname\nAs we shall see in chapter 3, the general notion of\nthe environment as providing a context in which evaluation takes place\nwill play an important role in our understanding of program execution.\n\nNotice that the evaluation rule given above does not handle For instance, evaluating\n\n```javascript\nconst x = 3;\n```\n\ndoes not apply\n\n```javascript\nan equality operator =\n```\n\nto two arguments, one of which is the value of the name declaration is precisely to associate\n\n```javascript\nconst x = 3;\n```\n\nis not a combination.)\n\nThe letters in const are\nrendered in bold to indicate that it\nkeyword in JavaScript.\n\nKeywords carry a\nparticular meaning, and thus cannot be used as names.\n\nA keyword or a\ncombination of keywords in a statement instructs the JavaScript\ninterpreter to treat the statement in a special way.\n\nEach such\nsyntactic form has its own evaluation rule.\n\nThe\nvarious kinds of statements and expressions (each with its associated\nevaluation rule) constitute the\n\nThe letters in const are\nrendered in bold to indicate that it\nkeyword in JavaScript.\n\nKeywords carry a\nparticular meaning, and thus cannot be used as names.\n\nA keyword or a\ncombination of keywords in a statement instructs the JavaScript\ninterpreter to treat the statement in a special way.\n\nEach such\nsyntactic form has its own evaluation rule.\n\nThe\nvarious kinds of statements and expressions (each with its associated\nevaluation rule) constitute the", - "token_count": 285, - "has_code": true, + "content": "Viewing evaluation in terms of the tree, we can imagine that the\nvalues of the operands percolate upward, starting from the terminal\nnodes and then combining at higher and higher levels.\n\nIn fact, the percolate values\nupward form of the evaluation rule is an example of a general kind\nof process known as\ntree accumulation.\n\nTree representation, showing the value of each subexpression.\n\nNext, observe that the repeated application of the first step brings\nus to the point where we need to evaluate, not combinations, but\nprimitive expressions such as\nnumerals or names.\n\nWe take care of the primitive cases\nby stipulating that\n-\n-\nthe values of numerals are the numbers that they name,\nand\n-\n-\nthe values of\nnames are the objects associated\nwith those names in the environment.\n\nThe key point to\nnotice is the role of the\nenvironment in determining the meaning of\nthe\nnames\nin expressions.\n\nIn an interactive language such as\nJavaScript,\nit is meaningless to speak of the value of an expression such as\nx + 1\nwithout specifying any information about the environment\nthat would provide a meaning for the\nname.\n\nAs we shall see in chapter 3, the general notion of\nthe environment as providing a context in which evaluation takes place\nwill play an important role in our understanding of program execution.\n\nNotice that the\nevaluation rule given above does not handle\ndeclarations.\n\nFor instance, evaluating\nconst x = 3;\ndoes not apply\nan equality operator =\nto two arguments, one\nof which is the value of the\nname\nand the other of which is\n3, since the purpose of the\ndeclaration\nis precisely to associate\nwith a value.\n\n(That is,\nconst x = 3;\nis not\na combination.)", + "token_count": 290, + "has_code": false, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", "subsection": "Evaluating Operator Combinations", @@ -1660,8 +1680,18 @@ "chunk_id": "Building_Abstractions_with_Functions_Evaluating_Operator_Combinations_2" }, { - "content": "Functions,\nas introduced above, are much like ordinary mathematical functions.\n\nThey\nspecify a value that is determined by one or more parameters.\n\nBut there\nis an important difference between mathematical functions and computer\nfunctions.\n\nComputer functions\nmust be effective.\n\nAs a case in point, consider the problem of computing square\nroots.\n\nWe can define the square-root function as\n\\[\n\\sqrt{x}\\ =\\text{ the }y\\text{ such that }y \\geq 0\\text{ and }\ny^2\\ =\\ x\n\\]\nThis describes a perfectly legitimate mathematical function.\n\nWe could\nuse it to recognize whether one number is the square root of another, or\nto derive facts about square roots in general.\n\nOn the other hand, the\ndefinition does not describe a\ncomputer function.\n\nIndeed, it tells us almost nothing about how to actually find the square\nroot of a given number.\n\nIt will not help matters to rephrase this\ndefinition in\npseudo-JavaScript:\n\n```javascript\nfunction sqrt(x) {\n return the y $\\texttt{with}$ y >= 0 && square(y) === x;\n}\n```\n\nThis only begs the question.\n\nThe contrast between mathematical function and computer function is a reflection of the general distinction between describing properties of things and describing how to do\n\nthings, or, as it is sometimes referred to, the distinction between\n\nHow does one compute\ns method of successive approximations, which says that whenever\nwe have a guess $y$ for the value of the square\nroot of a number $x$ , we can perform a simple\nmanipulation to get a better guess (one closer to the actual square root)\nby averaging $y$ with\n$x/y$.", - "token_count": 257, + "content": "(That is,\nconst x = 3;\nis not\na combination.)\n\nKeywords carry a\nparticular meaning, and thus cannot be used as names.\n\nA keyword or a\ncombination of keywords in a statement instructs the JavaScript\ninterpreter to treat the statement in a special way.\n\nEach such\nsyntactic form has its own evaluation rule.\n\nThe\nvarious kinds of statements and expressions (each with its associated\nevaluation rule) constitute the\nsyntax of the programming language.\n\nThe letters in const are\nrendered in bold to indicate that it\nis a\nkeyword in JavaScript.\n\nKeywords carry a\nparticular meaning, and thus cannot be used as names.\n\nA keyword or a\ncombination of keywords in a statement instructs the JavaScript\ninterpreter to treat the statement in a special way.\n\nEach such\nsyntactic form has its own evaluation rule.\n\nThe\nvarious kinds of statements and expressions (each with its associated\nevaluation rule) constitute the\nsyntax of the programming language.", + "token_count": 153, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Evaluating Operator Combinations", + "chunk_index": 3, + "chunk_id": "Building_Abstractions_with_Functions_Evaluating_Operator_Combinations_3" + }, + { + "content": "Functions,\nas introduced above, are much like ordinary mathematical functions.\n\nThey\nspecify a value that is determined by one or more parameters.\n\nBut there\nis an important difference between mathematical functions and computer\nfunctions.\n\nComputer functions\nmust be effective.\n\nAs a case in point, consider the problem of computing square\nroots.\n\nWe can define the square-root function as\n\\[\n\\sqrt{x}\\ =\\text{ the }y\\text{ such that }y \\geq 0\\text{ and }\ny^2\\ =\\ x\n\\]\nThis describes a perfectly legitimate mathematical function.\n\nWe could\nuse it to recognize whether one number is the square root of another, or\nto derive facts about square roots in general.\n\nOn the other hand, the\ndefinition does not describe a\ncomputer function.\n\nIndeed, it tells us almost nothing about how to actually find the square\nroot of a given number.\n\nIt will not help matters to rephrase this\ndefinition in\npseudo-JavaScript:\n\n```javascript\nfunction sqrt(x) {\n return the y $\\texttt{with}$ y >= 0 && square(y) === x;\n}\n```\n\nThis only begs the question.\n\nThe contrast between\nmathematical function and computer function\nis a reflection of the general distinction between describing properties of\nthings and describing how to do things, or, as it is sometimes referred to,\nthe distinction between\ndeclarative knowledge and imperative knowledge.\n\nIn\nmathematics we are usually concerned with declarative (what is)\ndescriptions, whereas in computer science we are usually concerned\nwith imperative (how to) descriptions.\n\nHow does one compute\nsquare roots?\n\nThe most common way is to use\nNewton s method of successive approximations, which says that whenever\nwe have a guess $y$ for the value of the square\nroot of a number $x$ , we can perform a simple\nmanipulation to get a better guess (one closer to the actual square root)\nby averaging $y$ with\n$x/y$.", + "token_count": 296, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", @@ -1670,8 +1700,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Example_Square_Roots_by_Newton_s_Method_1" }, { - "content": "How does one compute\ns method of successive approximations, which says that whenever\nwe have a guess $y$ for the value of the square\nroot of a number $x$ , we can perform a simple\nmanipulation to get a better guess (one closer to the actual square root)\nby averaging $y$ with\n$x/y$.\n\n1:\n\\[\n\\begin{array}{lll}\n\\textrm{Guess} & \\textrm{Quotient} & \\textrm{Average}\\\\[1em]\n1 & {\\displaystyle \\frac{2}{1} = 2} & {\\displaystyle \\frac{(2+1)}{2} = 1.5} \\\\[1em]\n1.5 & {\\displaystyle \\frac{2}{1.5} = 1.3333} & {\\displaystyle \\frac{(1.3333+1.5)}{2} = 1.4167} \\\\[1em]\n1.4167 & {\\displaystyle \\frac{2}{1.4167} = 1.4118} & {\\displaystyle \\frac{(1.4167+1.4118)}{2} = 1.4142} \\\\[1em]\n1.4142 & \\ldots & \\ldots\n\\end{array}\n\\]\nContinuing this process, we obtain better and better approximations to the\nsquare root.\n\nNow let s formalize the process in terms of functions.\n\nWe start with\na value for the\nfunction:\n\n```javascript\nsqrt_iter\n is_good_enough\n improve\n sqrt_iter_example\n\nfunction sqrt_iter(guess, x) {\n return is_good_enough(guess, x)\n ? guess\n : sqrt_iter(improve(guess, x), x);\n}\n```\n\n```javascript\nsqrt_iter_example\n\nsqrt_iter(3, 25);\n```\n\nA guess is improved by averaging it with the quotient of the radicand and the old guess:\n\n```javascript\nimprove\n average_definition\n improve_example\n\nfunction improve(guess, x) {\n return average(guess, x / guess);\n}\n```\n\n```javascript\nimprove_example\n\nimprove(3, 25);\n```\n\nwhere\n\n```javascript\naverage_definition\n average_example\n\nfunction average(x, y) {\n return (x + y) / 2;\n}\n```\n\n```javascript\naverage_example\n\naverage(3, 6);\n```\n\nWe also have to say what we mean by good enough.\n\nThe\nfollowing will do for illustration, but it is not really a very good\ntest.\n\n(See exercise.)\nThe idea is to improve the answer until it is close enough so that its\nsquare differs from the radicand by less than a predetermined\ntolerance (here 0.001):\n\n```javascript\nis_good_enough\n abs_definition\n square_definition\n is_good_enough_example\n\nfunction is_good_enough(guess, x) {\n return abs(square(guess) - x) < 0.001;\n}\n```\n\n```javascript\nis_good_enough_example\n\nis_good_enough(1.41, 2);\n```\n\nFinally, we need a way to get started.", - "token_count": 304, + "content": "The most common way is to use\nNewton s method of successive approximations, which says that whenever\nwe have a guess $y$ for the value of the square\nroot of a number $x$ , we can perform a simple\nmanipulation to get a better guess (one closer to the actual square root)\nby averaging $y$ with\n$x/y$.\n\nSuppose our\ninitial guess is 1:\n\\[\n\\begin{array}{lll}\n\\textrm{Guess} & \\textrm{Quotient} & \\textrm{Average}\\\\[1em]\n1 & {\\displaystyle \\frac{2}{1} = 2} & {\\displaystyle \\frac{(2+1)}{2} = 1.5} \\\\[1em]\n1.5 & {\\displaystyle \\frac{2}{1.5} = 1.3333} & {\\displaystyle \\frac{(1.3333+1.5)}{2} = 1.4167} \\\\[1em]\n1.4167 & {\\displaystyle \\frac{2}{1.4167} = 1.4118} & {\\displaystyle \\frac{(1.4167+1.4118)}{2} = 1.4142} \\\\[1em]\n1.4142 & \\ldots & \\ldots\n\\end{array}\n\\]\nContinuing this process, we obtain better and better approximations to the\nsquare root.\n\nNow let s formalize the process in terms of functions.\n\nWe start with\na value for the\nradicand (the number whose square root we are trying to compute) and a value\nfor the guess.\n\nIf the guess is good enough for our purposes, we are done;\nif not, we must repeat the process with an improved guess.\n\nWe write this\nbasic strategy as a\nfunction:\n\n```javascript\nsqrt_iter\n is_good_enough\n improve\n sqrt_iter_example\n\nfunction sqrt_iter(guess, x) {\n return is_good_enough(guess, x)\n ? guess\n : sqrt_iter(improve(guess, x), x);\n}\n```\n\n```javascript\nsqrt_iter_example\n\nsqrt_iter(3, 25);\n```\n\nA guess is improved by averaging it with the quotient of the radicand and the old guess:\n\n```javascript\nimprove\n average_definition\n improve_example\n\nfunction improve(guess, x) {\n return average(guess, x / guess);\n}\n```\n\n```javascript\nimprove_example\n\nimprove(3, 25);\n```\n\nwhere\n\n```javascript\naverage_definition\n average_example\n\nfunction average(x, y) {\n return (x + y) / 2;\n}\n```\n\n```javascript\naverage_example\n\naverage(3, 6);\n```\n\nWe also have to say what we mean by good enough.\n\nThe\nfollowing will do for illustration, but it is not really a very good\ntest.", + "token_count": 300, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", @@ -1680,8 +1710,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Example_Square_Roots_by_Newton_s_Method_2" }, { - "content": "Finally, we need a way to get started.\n\nFor instance, we can always guess\nthat the square root of any number\nis 1:\n\n```javascript\nsqrt\n sqrt_iter\n sqrt_example_2\n 2.2360688956433634\n\nfunction sqrt(x) {\n return sqrt_iter(1, x);\n}\n```\n\nIf we type these declarations to the interpreter, we can use function:\n\n```javascript\nsqrt_example\n sqrt\n\nsqrt(9);\n```\n\n```javascript\nsqrt_example_2\n\nsqrt(5);\n```\n\n```javascript\nsqrt_example_3\n sqrt\n\nsqrt(100 + 37);\n```\n\n```javascript\nsqrt_example_4\n 1.7739279023207892\n sqrt\n\nsqrt(sqrt(2) + sqrt(3));\n```\n\n```javascript\nsqrt_example_5\n sqrt\n\nsquare(sqrt(1000));\n```\n\nThe\nlanguage we have introduced so far is sufficient for writing any purely\nnumerical program that one could write in, say, C or Pascal.\n\nThis might\nseem surprising, since we have not included in our language any iterative\nThe function sqrt_iter,\non the other hand, demonstrates how iteration can be accomplished using no\nspecial construct other than the ordinary ability to call a\nfunction.", - "token_count": 141, + "content": "The\nfollowing will do for illustration, but it is not really a very good\ntest.\n\n```javascript\nis_good_enough\n abs_definition\n square_definition\n is_good_enough_example\n\nfunction is_good_enough(guess, x) {\n return abs(square(guess) - x) < 0.001;\n}\n```\n\n```javascript\nis_good_enough_example\n\nis_good_enough(1.41, 2);\n```\n\nFinally, we need a way to get started.\n\nFor instance, we can always guess\nthat the square root of any number\nis 1:\n\n```javascript\nsqrt\n sqrt_iter\n sqrt_example_2\n 2.2360688956433634\n\nfunction sqrt(x) {\n return sqrt_iter(1, x);\n}\n```\n\nIf we type these declarations to the interpreter, we can use just as we can use any function:\n\n```javascript\nsqrt_example\n sqrt\n\nsqrt(9);\n\n3.00009155413138\n```\n\n```javascript\nsqrt_example_2\n\nsqrt(5);\n```\n\n```javascript\nsqrt_example_3\n sqrt\n\nsqrt(100 + 37);\n\n11.704699917758145\n```\n\n```javascript\nsqrt_example_4\n 1.7739279023207892\n sqrt\n\nsqrt(sqrt(2) + sqrt(3));\n\n1.7739279023207892\n```\n\n```javascript\nsqrt_example_5\n sqrt\n\nsquare(sqrt(1000));\n\n1000.000369924366\n```\n\nThe program also illustrates that the\nsimple\nfunctional\nlanguage we have introduced so far is sufficient for writing any purely\nnumerical program that one could write in, say, C or Pascal.\n\nThis might\nseem surprising, since we have not included in our language any iterative\n(looping) constructs that direct the computer to do something over and over\nagain.\n\nThe function sqrt_iter,\non the other hand, demonstrates how iteration can be accomplished using no\nspecial construct other than the ordinary ability to call a\nfunction.", + "token_count": 208, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", @@ -1690,8 +1720,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Example_Square_Roots_by_Newton_s_Method_3" }, { - "content": "One easy way to get started at programming is to examine some typical\ninteractions with an interpreter for the\nJavaScript language.\n\nYou type\na statement,\nand the interpreter responds by displaying the\nresult of its evaluating that\n\n```javascript\nOne kind of statement you might type is an\n expression statement, which consists of an\n expression followed by a semicolon.\n```\n\n(More precisely, the expression that you type consists of the numerals that represent the number in base 10.) If you present JavaScript with the program\n\n```javascript\n486;\n```\n\nthe interpreter will respond by printing\n\nExpressions representing numbers may be combined with operators (such + * ) to form a\n\n```javascript\n137 + 349;\n```\n\n```javascript\n1000 - 334;\n```\n\n```javascript\n5 * 99;\n```\n\n```javascript\n10 / 4;\n```\n\n```javascript\n2.7 + 10;\n```\n\nExpressions such as these, which contain other expressions as components, are called combinations. operator symbol in the middle, and operand expressions to the left\n\nand right of it, are called operator combinations.\n\nThe convention of placing the operator between the operands is\nknown as\ninfix notation.\n\nIt follows the mathematical notation that\nyou are most likely familiar with from school and everyday life.\n\nAs in mathematics, operator combinations can be nested , that\nis, they can have operands that\n\n```javascript\n(3 * 5) + (10 - 6);\n```\n\nAs usual,\n\n```javascript\n3 * 5 + 10 / 2;\n```\n\nstands for\n\n```javascript\n(3 * 5) + (10 / 2);\n```\n\nWe say that * and\n/ have\nhigher precedence\nthan + and\n-.\n\nSequences of additions and\nsubtractions are read from left to right, as are sequences of\nmultiplications and divisions.\n\nThus,\n\n```javascript\n-6\n\n1 - 5 / 2 * 4 + 3;\n```\n\nstands for\n\n```javascript\n(1 - ((5 / 2) * 4)) + 3;\n```\n\nWe say that the operators + , - , * and / are left-associative.", - "token_count": 314, + "content": "One easy way to get started at programming is to examine some typical\ninteractions with an interpreter for the\nJavaScript language.\n\nYou type\na statement,\nand the interpreter responds by displaying the\nresult of its evaluating that\nstatement.\n\nOne kind of statement you might type is an statement, which consists of an expression followed by a semicolon.\n\nOne kind of primitive expression is a number.\n\n(More precisely, the expression that you type consists of the numerals that\nrepresent the number in base 10.)\nIf you present\nJavaScript with the program\n\n```javascript\n486;\n```\n\nthe interpreter will respond by printing\n\n```javascript\n486\n```\n\nExpressions representing numbers may be combined with\noperators\n(such\nas +\nor * ) to form a\ncompound expression that represents the\napplication of a corresponding primitive\nfunction to those numbers.\n\nFor example,\n\n```javascript\n137 + 349;\n\n486\n```\n\n```javascript\n1000 - 334;\n\n666\n```\n\n```javascript\n5 * 99;\n\n495\n```\n\n```javascript\n10 / 4;\n\n2.5\n```\n\n```javascript\n2.7 + 10;\n\n12.7\n```\n\nExpressions such as these, which contain other expressions\nas components, are called combinations.\n\nCombinations that are formed by an\noperator symbol in the middle, and\noperand expressions to the left and right of it,\nare called\noperator combinations.\n\nThe value of an operator combination is\nobtained by applying the function specified by the operator to the\narguments that are the values of the operands.\n\nThe convention of placing the operator between the operands is\nknown as\ninfix notation.\n\nIt follows the mathematical notation that\nyou are most likely familiar with from school and everyday life.\n\nAs in mathematics, operator combinations can be nested , that\nis, they can have operands that\nthemselves are operator combinations:\n\n```javascript\n(3 * 5) + (10 - 6);\n\n19\n```\n\nAs usual,\nparentheses are used to group operator combinations in order\nto avoid ambiguities.", + "token_count": 304, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", @@ -1700,8 +1730,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Expressions_1" }, { - "content": "We say that the operators + , - , * and / are left-associative.\n\nThere is no limit (in principle) to the depth of such nesting and to the\noverall complexity of the expressions that the JavaScript interpreter\ncan evaluate.\n\nIt is we humans who might get confused by still relatively\nsimple expressions such as\n\n```javascript\n57\n\n3 * 2 * (3 - 5 + 4) + 27 / 6 * 10;\n```\n\nwhich the interpreter would readily evaluate to be 57.\n\nWe can help\nourselves by writing such an expression in the form\n\n```javascript\n3 * 2 * (3 - 5 + 4)\n+\n27 / 6 * 10;\n```\n\nto visually separate the major components of the expression.\n\nEven with complex expressions, the interpreter always operates in the\nsame basic cycle: It reads\na statement typed by the user,\nevaluates the\nstatement,\nand prints the result.\n\nThis mode of operation is often expressed by saying\nthat the interpreter runs in a\nread-evaluate-print loop.\n\nObserve in particular that it is not necessary to explicitly instruct the\ninterpreter to print the value of the\nstatement.", - "token_count": 185, + "content": "As usual,\nparentheses are used to group operator combinations in order\nto avoid ambiguities.\n\nFor example,\n\n```javascript\n3 * 5 + 10 / 2;\n```\n\nstands for\n\n```javascript\n(3 * 5) + (10 / 2);\n```\n\nWe say that * and\n/ have\nhigher precedence\nthan + and\n-.\n\nSequences of additions and\nsubtractions are read from left to right, as are sequences of\nmultiplications and divisions.\n\nThus,\n\n```javascript\n-6\n\n1 - 5 / 2 * 4 + 3;\n```\n\nstands for\n\n```javascript\n(1 - ((5 / 2) * 4)) + 3;\n```\n\nWe say that the operators + , - , * and / are left-associative.\n\nThere is no limit (in principle) to the depth of such nesting and to the\noverall complexity of the expressions that the JavaScript interpreter\ncan evaluate.\n\nIt is we humans who might get confused by still relatively\nsimple expressions such as\n\n```javascript\n57\n\n3 * 2 * (3 - 5 + 4) + 27 / 6 * 10;\n```\n\nwhich the interpreter would readily evaluate to be 57.\n\nWe can help\nourselves by writing such an expression in the form\n\n```javascript\n3 * 2 * (3 - 5 + 4)\n+\n27 / 6 * 10;\n```\n\nto visually separate the major components of the expression.\n\nEven with complex expressions, the interpreter always operates in the\nsame basic cycle: It reads\na statement typed by the user,\nevaluates the\nstatement,\nand prints the result.\n\nThis mode of operation is often expressed by saying\nthat the interpreter runs in a\nread-evaluate-print loop.\n\nObserve in particular that it is not necessary to explicitly instruct the\ninterpreter to print the value of the\nstatement.", + "token_count": 278, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", @@ -1710,9 +1740,9 @@ "chunk_id": "Building_Abstractions_with_Functions_Expressions_2" }, { - "content": "The function sqrt\nis our first example of a process defined by a set of mutually\ndefined functions.\n\nNotice that the\n\n```javascript\ndeclaration of\n sqrt_iter\n```\n\nis\nrecursive ; that is, the\nfunction\nis defined in terms of itself.\n\nThe idea of being able to define a\nfunction\nin terms of itself may be disturbing; it may seem unclear how such a\ncircular definition could make sense at all, much less\nspecify a well-defined process to be carried out by a computer.\n\nThis will\nbe addressed more carefully in\nsection.\n\nBut first\nlet s consider some other important points illustrated by the\n\nObserve that the problem of computing square roots breaks up naturally\ninto a number of subproblems:\nfunction.\n\nThe entire\n\n```javascript\nfunctions\n\t(shown in figure)\n```\n\nthat mirrors the decomposition of the problem into subproblems.\n\nThe importance of this\nthe first ten lines, the next\nten lines, the next ten lines, and so on.\n\nRather, it is crucial that\neach\nfunction\naccomplishes an identifiable task that can be used as a module in defining\nother\nfunctions.\n\nFor example, when we define the\nis_good_enough function\nin terms of\nfunction\nas a\nblack box.\n\nWe are not at that moment concerned with\nhow the\nfunction\ncomputes its result, only with the fact that it computes the\nsquare.\n\nThe details of how the square is computed can be suppressed,\nto be considered at a later time.\n\nIndeed, as far as the\nis_good_enough function\nis concerned,\nfunction\nbut rather an abstraction of a\nfunction,\na so-called\nfunctional abstraction.\n\nAt this level of abstraction, any\nfunction\nthat computes the square is equally good.\n\nThus, considering only the values they return, the following two\nfunctions\nsquaring a number should be indistinguishable.\n\nEach takes a numerical\nargument and produces the square of that number as the value.", - "token_count": 301, - "has_code": true, + "content": "The function sqrt\nis our first example of a process defined by a set of mutually\ndefined functions.\n\nNotice that the\ndeclaration of sqrt_iter\nis\nrecursive ; that is, the\nfunction\nis defined in terms of itself.\n\nThe idea of being able to define a\nfunction\nin terms of itself may be disturbing; it may seem unclear how such a\ncircular definition could make sense at all, much less\nspecify a well-defined process to be carried out by a computer.\n\nThis will\nbe addressed more carefully in\nsection.\n\nBut first\nlet s consider some other important points illustrated by the\nexample.\n\nObserve that the problem of computing square roots breaks up naturally\ninto a number of subproblems:\nhow to tell whether a guess is good\nenough, how to improve a guess, and so on.\n\nEach of these tasks is\naccomplished by a separate\nfunction.\n\nThe entire program can be viewed as a\ncluster of\nfunctions (shown in figure)\nthat mirrors the decomposition of the problem into subproblems.\n\nThe importance of this\ndecomposition strategy is not simply that one\nis dividing the program into parts.\n\nAfter all, we could take any\nlarge program and divide it into parts the first ten lines, the next\nten lines, the next ten lines, and so on.\n\nRather, it is crucial that\neach\nfunction\naccomplishes an identifiable task that can be used as a module in defining\nother\nfunctions.\n\nFor example, when we define the\nis_good_enough function\nin terms of , we are able to\nregard the\nfunction\nas a\nblack box.\n\nWe are not at that moment concerned with\nhow the\nfunction\ncomputes its result, only with the fact that it computes the\nsquare.\n\nThe details of how the square is computed can be suppressed,\nto be considered at a later time.", + "token_count": 298, + "has_code": false, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", "subsection": "Functions as Black-Box Abstractions", @@ -1720,8 +1750,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Black-Box_Abstractions_1" }, { - "content": "Each takes a numerical\nargument and produces the square of that number as the value.\n\n```javascript\nsquare_example\n\nfunction square(x) {\n return x * x;\n}\n```\n\n```javascript\nsquare_example\n\nfunction square(x) {\n return math_exp(double(math_log(x)));\n}\nfunction double(x) {\n return x + x;\n}\n```\n\nSo a\nfunction\nshould be able to suppress detail.\n\nThe users of the\nfunction\nmay not have written the\nfunction\nthemselves, but may have obtained it from another programmer as a\nblack box.\n\nA user should not need to know how the\nfunction\nis implemented in order to use it.\n\nOne detail of a\nfunctions\nimplementation that should not matter to the user of the\nfunction\nis the implementer s choice of names for the\nfunctions parameters.\n\nThus, the following\nfunctions\nshould not be distinguishable:\n\n```javascript\nsquare_example\n\nfunction square(x) {\n return x * x;\n}\n```\n\n```javascript\nsquare_example\n\nfunction square(y) {\n return y * y;\n}\n```\n\nThis principle that the meaning of a\nfunction\nshould be independent of the parameter names used by its\nauthor seems on the surface to be self-evident, but its\nconsequences are profound.\n\nThe simplest consequence is that the\nparameter names of a\nfunction\nmust be local to the body of the\nfunction.\n\nFor example, we used\n\n```javascript\nin the declaration of\n is_good_enough\n```\n\nin our square-root function:\n\n```javascript\nis_good_enough_example\n abs_definition\n square_definition\n\nfunction is_good_enough(guess, x) {\n return abs(square(guess) - x) < 0.001;\n}\n```\n\nThe intention of the author of\nis_good_enough\nis to determine if the square of the first argument is within a given\ntolerance of the second argument.\n\nWe see that the author of\nis_good_enough\nused the name\nis_@good_@enough\nmust be a different\nfunction\nis_good_enough,\nbecause that value of\nis_good_enough\nafter\n\nIf the parameters were not local to the bodies of their respective functions, then the parameter is_@good_@enough, and the behavior of is_good_enough would depend upon\n\nwhich version of", - "token_count": 309, + "content": "The details of how the square is computed can be suppressed,\nto be considered at a later time.\n\nAt this level of abstraction, any\nfunction\nthat computes the square is equally good.\n\nThus, considering only the values they return, the following two\nfunctions\nsquaring a number should be indistinguishable.\n\nEach takes a numerical\nargument and produces the square of that number as the value.\n\n```javascript\nsquare_example\n\nfunction square(x) {\n return x * x;\n}\n```\n\n```javascript\nsquare_example\n\nfunction square(x) {\n return math_exp(double(math_log(x)));\n}\nfunction double(x) {\n return x + x;\n}\n```\n\nSo a\nfunction\nshould be able to suppress detail.\n\nThe users of the\nfunction\nmay not have written the\nfunction\nthemselves, but may have obtained it from another programmer as a\nblack box.\n\nA user should not need to know how the\nfunction\nis implemented in order to use it.\n\nOne detail of a\nfunctions\nimplementation that should not matter to the user of the\nfunction\nis the implementer s choice of names for the\nfunctions parameters.\n\nThus, the following\nfunctions\nshould not be distinguishable:\n\n```javascript\nsquare_example\n\nfunction square(x) {\n return x * x;\n}\n```\n\n```javascript\nsquare_example\n\nfunction square(y) {\n return y * y;\n}\n```\n\nThis principle that the meaning of a\nfunction\nshould be independent of the parameter names used by its\nauthor seems on the surface to be self-evident, but its\nconsequences are profound.\n\nThe simplest consequence is that the\nparameter names of a\nfunction\nmust be local to the body of the\nfunction.\n\nFor example, we used\nin the declaration of is_good_enough\nin our square-root\nfunction:\n\n```javascript\nis_good_enough_example\n abs_definition\n square_definition\n\nfunction is_good_enough(guess, x) {\n return abs(square(guess) - x) < 0.001;\n}\n```\n\nThe intention of the author of\nis_good_enough\nis to determine if the square of the first argument is within a given\ntolerance of the second argument.", + "token_count": 305, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", @@ -1730,9 +1760,9 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Black-Box_Abstractions_2" }, { - "content": "which version of\n\nA\nparameter of a function\nhas a very special role in the\nfunction declaration,\nin that it doesn t matter what name the\nparameter has.\n\nSuch a name is called\nbound, and we say that the function declaration\nbinds its\nparameters.\n\nThe meaning of a\nfunction declaration is unchanged if a bound name\nis consistently renamed throughout the\ndeclaration.\nname\nis not bound, we say that it is\nfree.\n\nThe set of\nstatements\nfor which a binding\ndeclares\na name is called the\nscope of that name.\n\nIn a\nfunction declaration, the bound names\ndeclared as the\nparameters of the function\nhave the body of the\nfunction\nas their scope.\n\nIn the\ndeclaration of is_good_enough\nabove,\nnames\nbut\nis_good_enough\nshould be independent of the names we choose for\ncapturing the\nname\nis_good_enough\nis not independent of the\nchoice of its free names,\nhowever.\n\nIt surely depends upon the fact\n(external to this declaration)\nthat the name\nfor computing the absolute value of a number.\n\nThe function is_good_enough\nwill compute a different function if we substitute\n\n```javascript\nmath_cos\n\t(the primitive cosine function)\n```\n\nfor declaration.\n\nWe have one kind of name isolation available to us so far:\nThe parameters of a function\nare local to the body of the\nfunction.\n\nThe square-root program illustrates another way in which we would like to\ncontrol the use of names.\nfunctions:\n\n```javascript\nsqrt_example_2\n abs_definition\n square_definition\n average_definition\n\nfunction sqrt(x) {\n return sqrt_iter(1, x);\n}\nfunction sqrt_iter(guess, x) {\n return is_good_enough(guess, x)\n ? guess\n : sqrt_iter(improve(guess, x), x);\n}\nfunction is_good_enough(guess, x) {\n return abs(square(guess) - x) < 0.001;\n}\nfunction improve(guess, x) {\n return average(guess, x / guess);\n}\n```\n\nThe problem with this program is that the only function that is important to users of functions\n\n```javascript\n(sqrt_iter,\n\tis_good_enough,\n```", - "token_count": 299, - "has_code": true, + "content": "The intention of the author of\nis_good_enough\nis to determine if the square of the first argument is within a given\ntolerance of the second argument.\n\nThe argument of\nis.\n\nIf the author of\nused\n(as above) to refer to that argument, we see that the\nin\nis_@good_@enough\nmust be a different than the one\nin.\n\nRunning the\nfunction\nmust not affect the value\nof that is used by\nis_good_enough,\nbecause that value of may be needed by\nis_good_enough\nafter is done computing.\n\nIf the parameters were not local to the bodies of their respective\nfunctions,\nthen the parameter in\ncould be confused with the parameter\nin\nis_@good_@enough,\nand the behavior of\nis_good_enough\nwould depend upon which version of\nwe used.\n\nThus, would not be the\nblack box we desired.\n\nA\nparameter of a function\nhas a very special role in the\nfunction declaration,\nin that it doesn t matter what name the\nparameter has.\n\nSuch a name is called\nbound, and we say that the function declaration\nbinds its\nparameters.\n\nThe meaning of a\nfunction declaration is unchanged if a bound name\nis consistently renamed throughout the\ndeclaration.\n\nIf a\nname\nis not bound, we say that it is\nfree.\n\nThe set of\nstatements\nfor which a binding\ndeclares\na name is called the\nscope of that name.\n\nIn a\nfunction declaration, the bound names\ndeclared as the\nparameters of the function\nhave the body of the\nfunction\nas their scope.\n\nIn the\ndeclaration of is_good_enough\nabove,\nand\nare\nbound\nnames\nbut\nand are free.\n\nThe meaning of\nis_good_enough\nshould be independent of the names we choose for\nand\nso long as they are distinct and\ndifferent from\nand.\n\n(If we renamed\nto\nwe would have introduced a bug by\ncapturing the\nname.", + "token_count": 294, + "has_code": false, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", "subsection": "Functions as Black-Box Abstractions", @@ -1740,8 +1770,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Black-Box_Abstractions_3" }, { - "content": "The problem with this program is that the only function that is important to users of functions\n\nand\ndeclare any other function\ncalled\nis_good_enough\nas part of another program to work together\nwith the square-root program, because\nfunctions,\nmany numerical functions are computed as successive approximations and\nthus might have\nfunctions\nnamed\nis_good_enough\nand\nfunctions.\n\nWe would like to localize the\nsubfunctions,\nhiding them inside\nis_good_enough function.\n\nTo make this possible, we allow a\nfunction\nto have\ninternal declarations that are local to that\nfunction.\n\nFor example, in the square-root problem we can write\n\n```javascript\nsqrt_example_2\n 2.2360688956433634\n abs_definition\n square_definition\n average_definition\n\nfunction sqrt(x) {\n function is_good_enough(guess, x) {\n return abs(square(guess) - x) < 0.001;\n }\n function improve(guess, x) {\n return average(guess, x / guess);\n }\n function sqrt_iter(guess, x) {\n return is_good_enough(guess, x)\n ? guess\n : sqrt_iter(improve(guess, x), x);\n }\n return sqrt_iter(1, x);\n}\n```\n\n```javascript\nAny matching pair of braces designates a block, and\n\tdeclarations inside the block are local to the block.\n```\n\nSuch nesting of\ndeclarations,\ncalled block structure , is basically the right solution to the\nsimplest name-packaging problem.\n\nBut there is a better idea lurking here.\n\nIn addition to internalizing the\ndeclarations of the auxiliary functions,\nwe can simplify them.\n\nSince\ndeclaration\nof\nfunctions\nis_good_enough,\n\n```javascript\nsqrt_iter,\n which are declared internally to\n```\n\nfunctions.\n\nInstead, we allow\nname\nin the internal\ndeclarations,\nas shown below.\n\nThen\nfunction\nlexical scoping.\n\n```javascript\nsqrt_example_2\n abs_definition\n square_definition\n average_definition\n\nfunction sqrt(x) {\n function is_good_enough(guess) {\n return abs(square(guess) - x) < 0.001;\n }\n function improve(guess) {\n return average(guess, x / guess);\n }\n function sqrt_iter(guess) {\n return is_good_enough(guess)\n ? guess\n : sqrt_iter(improve(guess));\n }\n return sqrt_iter(1);\n}\n```\n\nWe will use block structure extensively to help us break up large programs\ninto tractable pieces.\n\n60.", - "token_count": 290, + "content": "(If we renamed\nto\nwe would have introduced a bug by\ncapturing the\nname.\n\nIt surely depends upon the fact\n(external to this declaration)\nthat the name refers to a function\nfor computing the absolute value of a number.\n\nThe function is_good_enough\nwill compute a different function if we substitute\nmath_cos (the primitive cosine function)\nfor in its\ndeclaration.\n\nWe have one kind of name isolation available to us so far:\nThe parameters of a function\nare local to the body of the\nfunction.\n\nThe square-root program illustrates another way in which we would like to\ncontrol the use of names.\n\nThe existing program consists of separate\nfunctions:\n\n```javascript\nsqrt_example_2\n abs_definition\n square_definition\n average_definition\n\nfunction sqrt(x) {\n return sqrt_iter(1, x);\n}\nfunction sqrt_iter(guess, x) {\n return is_good_enough(guess, x)\n ? guess\n : sqrt_iter(improve(guess, x), x);\n}\nfunction is_good_enough(guess, x) {\n return abs(square(guess) - x) < 0.001;\n}\nfunction improve(guess, x) {\n return average(guess, x / guess);\n}\n```\n\nThe problem with this program is that the only\nfunction\nthat is important to users of is.\n\nThe other\nfunctions\n(sqrt_iter, is_good_enough,\nand ) only clutter up their minds.\n\nThey may not\ndeclare any other function\ncalled\nis_good_enough\nas part of another program to work together\nwith the square-root program, because\nneeds it.\n\nThe problem is especially severe in the construction of large\nsystems by many separate programmers.\n\nFor example, in the construction\nof a large library of numerical\nfunctions,\nmany numerical functions are computed as successive approximations and\nthus might have\nfunctions\nnamed\nis_good_enough\nand as auxiliary\nfunctions.\n\nWe would like to localize the\nsubfunctions,\nhiding them inside so that\ncould coexist with other\nsuccessive approximations, each having its own private\nis_good_enough function.\n\nTo make this possible, we allow a\nfunction\nto have\ninternal declarations that are local to that\nfunction.", + "token_count": 298, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", @@ -1750,9 +1780,9 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Black-Box_Abstractions_4" }, { - "content": "60.\n\nIt appears in most advanced programming languages and is an\nimportant tool for helping to organize the construction of large programs.", - "token_count": 22, - "has_code": false, + "content": "To make this possible, we allow a\nfunction\nto have\ninternal declarations that are local to that\nfunction.\n\n```javascript\nsqrt_example_2\n 2.2360688956433634\n abs_definition\n square_definition\n average_definition\n\nfunction sqrt(x) {\n function is_good_enough(guess, x) {\n return abs(square(guess) - x) < 0.001;\n }\n function improve(guess, x) {\n return average(guess, x / guess);\n }\n function sqrt_iter(guess, x) {\n return is_good_enough(guess, x)\n ? guess\n : sqrt_iter(improve(guess, x), x);\n }\n return sqrt_iter(1, x);\n}\n```\n\nAny matching pair of braces designates a block, and declarations inside the block are local to the block.\n\nSuch nesting of\ndeclarations,\ncalled block structure , is basically the right solution to the\nsimplest name-packaging problem.\n\nBut there is a better idea lurking here.\n\nIn addition to internalizing the\ndeclarations of the auxiliary functions,\nwe can simplify them.\n\nSince is bound in the\ndeclaration\nof , the\nfunctions\nis_good_enough,\n, and\nsqrt_iter, which are declared internally to\n, are in the scope of.\n\nThus, it is not necessary to pass\nexplicitly to each of these\nfunctions.\n\nInstead, we allow to be a free\nname\nin the internal\ndeclarations,\nas shown below.\n\nThen gets its value from\nthe argument with which the enclosing\nfunction\nis called.\n\nThis discipline is called\nlexical scoping.\n\n```javascript\nsqrt_example_2\n abs_definition\n square_definition\n average_definition\n\nfunction sqrt(x) {\n function is_good_enough(guess) {\n return abs(square(guess) - x) < 0.001;\n }\n function improve(guess) {\n return average(guess, x / guess);\n }\n function sqrt_iter(guess) {\n return is_good_enough(guess)\n ? guess\n : sqrt_iter(improve(guess));\n }\n return sqrt_iter(1);\n}\n```\n\nWe will use block structure extensively to help us break up large programs\ninto tractable pieces.\n\nThe idea of block structure originated with the programming language\nAlgol 60.\n\nIt appears in most advanced programming languages and is an\nimportant tool for helping to organize the construction of large programs.", + "token_count": 288, + "has_code": true, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", "subsection": "Functions as Black-Box Abstractions", @@ -1760,8 +1790,8 @@ "chunk_id": "Building_Abstractions_with_Functions_Functions_as_Black-Box_Abstractions_5" }, { - "content": "A critical aspect of a programming language is the means it provides for using objects, and our first such means are constants. constant whose value\n\nis the object.\n\n```javascript\nIn JavaScript, we name constants with\n\tconstant declarations.\n```\n\n```javascript\nvar_size\n\nconst size = 2;\n```\n\ncauses the interpreter to associate the value 2 with the name\n\n```javascript\nsize_use_1\n var_size\n 2\n\nsize;\n```\n\n```javascript\nsize_use_2\n var_size\n 10\n\n5 * size;\n```\n\nHere are further examples of the use of const:\n\n```javascript\npi\n\nconst pi = 3.14159;\n```\n\n```javascript\nradius\n\nconst radius = 10;\n```\n\n```javascript\npi_radius_radius\n 314.159\n pi\n radius\n\npi * radius * radius;\n```\n\n```javascript\ncircumference_definition\n pi\n radius\n\nconst circumference = 2 * pi * radius;\n```\n\n```javascript\n62.8318\n circumference_use\n circumference_definition\n\ncircumference;\n```\n\nConstant is our language s simplest means of abstraction, for it allows us to use simple names to refer to the results of compound operations,\n\nsuch as the JavaScript program usually consists of a large number of relatively simple functions.\n\nIt should be clear that the possibility of associating values with\nnames and later retrieving them means that the interpreter must\nmaintain some sort of memory that keeps track of the name-object\npairs.\n\nThis memory is called the\nenvironment\n(more precisely the\nprogram environment,\nsince we will see later that a\ncomputation may involve a number of different\nenvironments).", - "token_count": 222, + "content": "A critical aspect of a programming language is the means it provides\nfor using\nnames to refer to computational\nobjects, and our first such means are constants.\n\nWe say that the\nname identifies a\nconstant\nwhose\nvalue is the object.\n\nIn JavaScript, we name constants with constant declarations.\n\n```javascript\nvar_size\n\nconst size = 2;\n```\n\ncauses the interpreter to associate the value 2 with the\nname.\n\nOnce the name\nhas been associated with the number 2, we can\nrefer to the value 2 by name:\n\n```javascript\nsize_use_1\n var_size\n 2\n\nsize;\n\n2\n```\n\n```javascript\nsize_use_2\n var_size\n 10\n\n5 * size;\n\n10\n```\n\nHere are further examples of the use of const:\n\n```javascript\npi\n\nconst pi = 3.14159;\n```\n\n```javascript\nradius\n\nconst radius = 10;\n```\n\n```javascript\npi_radius_radius\n 314.159\n pi\n radius\n\npi * radius * radius;\n\n314.159\n```\n\n```javascript\ncircumference_definition\n pi\n radius\n\nconst circumference = 2 * pi * radius;\n```\n\n```javascript\n62.8318\n circumference_use\n circumference_definition\n\ncircumference;\n\n62.8318\n```\n\nConstant declaration\nis our language s\nsimplest means of abstraction, for it allows us to use simple names to\nrefer to the results of compound operations, such as the\ncomputed above.\n\nIn general, computational objects may have very complex\nstructures, and it would be extremely inconvenient to have to remember\nand repeat their details each time we want to use them.\n\nIndeed,\ncomplex programs are constructed by building, step by step,\ncomputational objects of increasing complexity.\n\nThe\ninterpreter makes this step-by-step program construction particularly\nconvenient because name-object associations can be created\nincrementally in successive interactions.\n\nThis feature encourages the\nincremental development and testing of programs and is largely\nresponsible for the fact that a\nJavaScript\nprogram usually consists of a large\nnumber of relatively simple\nfunctions.\n\nIt should be clear that the possibility of associating values with\nnames and later retrieving them means that the interpreter must\nmaintain some sort of memory that keeps track of the name-object\npairs.", + "token_count": 315, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", @@ -1770,8 +1800,18 @@ "chunk_id": "Building_Abstractions_with_Functions_Naming_and_the_Environment_1" }, { - "content": "```javascript\nTo evaluate a function application, the interpreter follows the process\n\tdescribed in section.\n```\n\nThat is, the interpreter evaluates the elements of the application and applies the function (which is the value of the function expression of the application)\n\nto the arguments (which are the values of the argument expressions of the application).\n\n```javascript\nWe can assume that the application of primitive\n\tfunctions is handled by the interpreter or libraries.\n```\n\nFor compound\nfunctions,\nthe application process is as follows:\n-\n-\nTo apply a compound\nfunction\nto arguments,\nevaluate the return expression of the function\nwith each\nparameter replaced by the corresponding argument.\n\nTo illustrate this process, let s evaluate the\napplication\n\n```javascript\nf_of_five\n f\n 136\n\nf(5)\n\nf(5);\n```\n\nwhere\nfunction declared\nin section.\n\nWe begin by retrieving the\nreturn expression\nof\n\n```javascript\nf\n\nsum_of_squares(a + 1, a * 2)\n```\n\nThen we replace the parameter\n\n```javascript\nsum_of_squares(5 + 1, 5 * 2)\n```\n\nThus the problem reduces to the evaluation of an application with two arguments and\n\n```javascript\na function expression\n\tsum_of_squares.\n```\n\nEvaluating this\napplication\ninvolves three subproblems.\n\nWe must evaluate the\nfunction expression\nto get the\nfunction\nto be applied, and we must evaluate the\nargument expressions\nto get the arguments.\n\nNow\n5 + 1\nproduces 6 and\n5 * 2\nproduces 10, so we must apply the\nsum_of_squares function\nto 6 and 10.\n\nThese values are substituted for the\nparameters\nsum_of_squares,\nreducing the expression to\n\n```javascript\nsquare(6) + square(10)\n```\n\nIf we use the declaration of\n\n```javascript\n(6 * 6) + (10 * 10)\n```\n\nwhich reduces by multiplication to\n\n```javascript\n36 + 100\n```\n\nand finally to\n\n```javascript\n136\n```\n\nThe process we have just described is called the substitution\nmodel for\nfunction\napplication.\n\nIt can be taken as a model that\ndetermines the meaning of\nfunction\napplication, insofar as the\nfunctions\nin this chapter are concerned.", - "token_count": 314, + "content": "It should be clear that the possibility of associating values with\nnames and later retrieving them means that the interpreter must\nmaintain some sort of memory that keeps track of the name-object\npairs.", + "token_count": 33, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "Naming and the Environment", + "chunk_index": 2, + "chunk_id": "Building_Abstractions_with_Functions_Naming_and_the_Environment_2" + }, + { + "content": "To evaluate a function application, the interpreter follows the process described in section.\n\nThat is, the interpreter evaluates the elements of the\napplication\nand applies the\nfunction\n(which is the value of the\nfunction expression of the application)\nto the arguments (which are the values of the\nargument expressions of the application).\n\nWe can assume that the application of primitive functions is handled by the interpreter or libraries.\n\nFor compound\nfunctions,\nthe application process is as follows:\n-\n-\nTo apply a compound\nfunction\nto arguments,\nevaluate the return expression of the function\nwith each\nparameter replaced by the corresponding argument.\n\nTo illustrate this process, let s evaluate the\napplication\n\n```javascript\nf_of_five\n f\n 136\n\nf(5)\n\nf(5);\n```\n\nwhere is the\nfunction declared\nin section.\n\nWe begin by retrieving the\nreturn expression\nof :\n\n```javascript\nf\n\nsum_of_squares(a + 1, a * 2)\n```\n\nThen we replace the parameter by the argument 5:\n\n```javascript\nsum_of_squares(5 + 1, 5 * 2)\n```\n\nThus the problem reduces to the evaluation of\nan application\nwith two\narguments\nand\na function expression sum_of_squares.\n\nEvaluating this\napplication\ninvolves three subproblems.\n\nWe must evaluate the\nfunction expression\nto get the\nfunction\nto be applied, and we must evaluate the\nargument expressions\nto get the arguments.\n\nNow\n5 + 1\nproduces 6 and\n5 * 2\nproduces 10, so we must apply the\nsum_of_squares function\nto 6 and 10.\n\nThese values are substituted for the\nparameters and\nin the body of\nsum_of_squares,\nreducing the expression to\n\n```javascript\nsquare(6) + square(10)\n```\n\nIf we use the declaration of , this reduces to\n\n```javascript\n(6 * 6) + (10 * 10)\n```\n\nwhich reduces by multiplication to\n\n```javascript\n36 + 100\n```\n\nand finally to\n\n```javascript\n136\n```\n\nThe process we have just described is called the substitution\nmodel for\nfunction\napplication.", + "token_count": 301, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", @@ -1780,9 +1820,9 @@ "chunk_id": "Building_Abstractions_with_Functions_The_Substitution_Model_for_Function_Application_1" }, { - "content": "It can be taken as a model that\ndetermines the meaning of\nfunction\napplication, insofar as the\nfunctions\nin this chapter are concerned.\n\nHowever, there are two\npoints that should be stressed:\n-\n-\nThe purpose of the substitution is to help us think about\nfunction\napplication, not to provide a description of how the interpreter\nreally works.\n\nTypical interpreters do not evaluate\nfunction\napplications by manipulating the text of a\nfunction to substitute values for the\nparameters.\n\nIn practice, the substitution is\naccomplished by using a local environment for the\nparameters.\n\nWe will discuss this more fully in chapters 3 and\n4 when we examine the implementation of an interpreter in detail.\n-\n-\nOver the course of this book, we will present a sequence of\nincreasingly elaborate models of how interpreters work, culminating\nwith a complete implementation of an interpreter and compiler in\nchapter.\n\nThe substitution model is only the first of\nthese models a way to get started thinking formally\nabout the evaluation process.\n\nIn general, when\nthe use of\nfunctions\nwith mutable data, we will see that the substitution\nmodel breaks down and must be replaced by a more complicated model of\nfunction\napplication.\n\nAccording to the description of evaluation given in\nsection,\nthe interpreter first evaluates the\nfunction\nand\nargument expressions\nand then applies the resulting\nfunction\nto the resulting arguments.\n\nThis is not the only way to perform evaluation.\n\nAn alternative evaluation model would not evaluate the\narguments\nuntil their values were needed.\n\nInstead it would first substitute\nargument\nexpressions for parameters until it obtained an expression involving\nonly\noperators and primitive functions,\nand would then perform the evaluation.\n\nIf we\nused this method, the evaluation of\n\n```javascript\nf(5)\n```\n\nwould proceed according to the sequence of expansions", - "token_count": 294, - "has_code": true, + "content": "The process we have just described is called the substitution\nmodel for\nfunction\napplication.\n\nHowever, there are two\npoints that should be stressed:\n-\n-\nThe purpose of the substitution is to help us think about\nfunction\napplication, not to provide a description of how the interpreter\nreally works.\n\nTypical interpreters do not evaluate\nfunction\napplications by manipulating the text of a\nfunction to substitute values for the\nparameters.\n\nIn practice, the substitution is\naccomplished by using a local environment for the\nparameters.\n\nWe will discuss this more fully in chapters 3 and\n4 when we examine the implementation of an interpreter in detail.\n-\n-\nOver the course of this book, we will present a sequence of\nincreasingly elaborate models of how interpreters work, culminating\nwith a complete implementation of an interpreter and compiler in\nchapter.\n\nThe substitution model is only the first of\nthese models a way to get started thinking formally\nabout the evaluation process.\n\nIn general, when\nmodeling phenomena in science and engineering, we begin with\nsimplified, incomplete models.\n\nAs we examine things in greater detail,\nthese simple models become inadequate and must be replaced by more\nrefined models.\n\nThe substitution model is no exception.\n\nIn particular,\nwhen we address in chapter the use of\nfunctions\nwith mutable data, we will see that the substitution\nmodel breaks down and must be replaced by a more complicated model of\nfunction\napplication.\n\nAccording to the description of evaluation given in\nsection,\nthe interpreter first evaluates the\nfunction\nand\nargument expressions\nand then applies the resulting\nfunction\nto the resulting arguments.\n\nThis is not the only way to perform evaluation.\n\nAn alternative evaluation model would not evaluate the\narguments\nuntil their values were needed.", + "token_count": 285, + "has_code": false, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", "subsection": "The Substitution Model for Function Application", @@ -1790,8 +1830,8 @@ "chunk_id": "Building_Abstractions_with_Functions_The_Substitution_Model_for_Function_Application_2" }, { - "content": "would proceed according to the sequence of expansions\n\n```javascript\nsum_of_squares(5 + 1, 5 * 2)\n\nsquare(5 + 1) + square(5 * 2)\n\n(5 + 1) * (5 + 1) + (5 * 2) * (5 * 2)\n```\n\nfollowed by the reductions\n\n```javascript\n6 * 6 + 10 * 10\n\n 36 + 100\n\n 136\n```\n\nThis gives the same answer as our previous evaluation model, but the\nprocess is different.\n\nIn particular, the evaluations of\n5 + 1\nand\n5 * 2\nare each performed twice here, corresponding to the reduction of the\nexpression\n\n```javascript\nx * x\n```\n\nwith 5 + 1 and 5 * 2.\n\nThis alternative fully expand and then reduce\nevaluation method is known as\nnormal-order evaluation , in contrast to the evaluate\nthe arguments and then apply method that the interpreter actually\nuses, which is called\napplicative-order evaluation.\n\nIt can be shown that, for\nfunction\napplications that can be modeled using substitution (including all the\nfunctions\nin the first two chapters of this book) and that yield legitimate values,\nnormal-order and applicative-order evaluation produce the same value.\n\n(See exercise\nfor an instance of an illegitimate value where normal-order\nand applicative-order evaluation do not give the same result.)\n\nJavaScript uses applicative-order evaluation, partly because of the additional efficiency obtained from avoiding multiple evaluations of expressions such as those illustrated with\n\n```javascript\n5 + 1\n\tand 5 * 2\n```\n\nabove and, more significantly, because normal-order evaluation\nbecomes much more complicated to deal with when we leave the realm of\nfunctions\nthat can be modeled by substitution.\n\nOn the other hand,\nnormal-order evaluation can be an extremely valuable tool, and we will\ninvestigate some of its implications in chapters 3 and 4.", - "token_count": 285, + "content": "An alternative evaluation model would not evaluate the\narguments\nuntil their values were needed.\n\nIf we\nused this method, the evaluation of\n\n```javascript\nf(5)\n```\n\nwould proceed according to the sequence of expansions\n\n```javascript\nsum_of_squares(5 + 1, 5 * 2)\n\nsquare(5 + 1) + square(5 * 2)\n\n(5 + 1) * (5 + 1) + (5 * 2) * (5 * 2)\n```\n\nfollowed by the reductions\n\n```javascript\n6 * 6 + 10 * 10\n\n 36 + 100\n\n 136\n```\n\nThis gives the same answer as our previous evaluation model, but the\nprocess is different.\n\nIn particular, the evaluations of\n5 + 1\nand\n5 * 2\nare each performed twice here, corresponding to the reduction of the\nexpression\n\n```javascript\nx * x\n```\n\nwith replaced respectively by 5 + 1 and 5 * 2.\n\nThis alternative fully expand and then reduce\nevaluation method is known as\nnormal-order evaluation , in contrast to the evaluate\nthe arguments and then apply method that the interpreter actually\nuses, which is called\napplicative-order evaluation.\n\nIt can be shown that, for\nfunction\napplications that can be modeled using substitution (including all the\nfunctions\nin the first two chapters of this book) and that yield legitimate values,\nnormal-order and applicative-order evaluation produce the same value.\n\n(See exercise\nfor an instance of an illegitimate value where normal-order\nand applicative-order evaluation do not give the same result.)\n\nJavaScript\nuses applicative-order evaluation, partly because of the\nadditional efficiency obtained from avoiding multiple evaluations of\nexpressions such as those illustrated with\n5 + 1 and 5 * 2\nabove and, more significantly, because normal-order evaluation\nbecomes much more complicated to deal with when we leave the realm of\nfunctions\nthat can be modeled by substitution.", + "token_count": 286, "has_code": true, "chapter": "Building Abstractions with Functions", "section": "The Elements of Programming", @@ -1800,9 +1840,19 @@ "chunk_id": "Building_Abstractions_with_Functions_The_Substitution_Model_for_Function_Application_3" }, { - "content": "We began this book by studying processes and by describing processes\nin terms of\nfunctions\nwritten in\nJavaScript.\n\nTo explain the meanings of these\nfunctions,\nwe used a succession of models of evaluation: the\nsubstitution model of chapter , the environment model of\nchapter , and the metacircular evaluator of chapter.\n\nOur\nexamination of the metacircular evaluator, in particular, dispelled much of\nthe mystery of how\nJavaScript-like languages are interpreted.\n\nBut even the metacircular evaluator leaves important questions\nunanswered, because it fails to elucidate the mechanisms of control in a\nJavaScript\nsystem.\n\nFor instance, the evaluator does not explain how the\nevaluation of a subexpression manages to return a value to the\nexpression that uses this value.\n\n```javascript\nAlso, the evaluator does not explain how some recursive\n functions can generate iterative processes (that is, be\n evaluated using constant space) whereas other recursive\n functions will generate recursive processes.\n```\n\nWe\nwill describe processes in terms of the step-by-step\noperation of a traditional computer.\n\nSuch a computer, or\nregister machine , sequentially executes\ninstructions that\nmanipulate the contents of a fixed set of storage elements called\nregisters.\n\nA typical register-machine instruction applies a\nprimitive operation to the contents of some registers and assigns the\nresult to another register.\n\nOur descriptions of processes executed by\nregister machines will look very much like machine-language\nprograms for traditional computers.\n\nHowever, instead of focusing on\nthe machine language of any particular computer, we will examine\nseveral\nJavaScript\nfunctions\nand design a specific register machine to\nexecute each\nfunction.\n\nThus, we will approach our task from the\nperspective of a hardware architect rather than that of a\nmachine-language computer programmer.\n\nIn designing register machines,\nwe will develop mechanisms for implementing important programming\nconstructs such as recursion.\n\nWe will also present a language for\ndescribing designs for register machines.", - "token_count": 301, - "has_code": true, + "content": "JavaScript\nuses applicative-order evaluation, partly because of the\nadditional efficiency obtained from avoiding multiple evaluations of\nexpressions such as those illustrated with\n5 + 1 and 5 * 2\nabove and, more significantly, because normal-order evaluation\nbecomes much more complicated to deal with when we leave the realm of\nfunctions\nthat can be modeled by substitution.", + "token_count": 56, + "has_code": false, + "chapter": "Building Abstractions with Functions", + "section": "The Elements of Programming", + "subsection": "The Substitution Model for Function Application", + "chunk_index": 4, + "chunk_id": "Building_Abstractions_with_Functions_The_Substitution_Model_for_Function_Application_4" + }, + { + "content": "We began this book by studying processes and by describing processes\nin terms of\nfunctions\nwritten in\nJavaScript.\n\nTo explain the meanings of these\nfunctions,\nwe used a succession of models of evaluation: the\nsubstitution model of chapter , the environment model of\nchapter , and the metacircular evaluator of chapter.\n\nOur\nexamination of the metacircular evaluator, in particular, dispelled much of\nthe mystery of how\nJavaScript-like languages are interpreted.\n\nBut even the metacircular evaluator leaves important questions\nunanswered, because it fails to elucidate the mechanisms of control in a\nJavaScript\nsystem.\n\nFor instance, the evaluator does not explain how the\nevaluation of a subexpression manages to return a value to the\nexpression that uses this value.\n\nAlso, the evaluator does not explain how some recursive functions can generate iterative processes (that is, be evaluated using constant space) whereas other recursive functions will generate recursive processes.\n\nThis chapter addresses both of these issues.\n\nWe\nwill describe processes in terms of the step-by-step\noperation of a traditional computer.\n\nSuch a computer, or\nregister machine , sequentially executes\ninstructions that\nmanipulate the contents of a fixed set of storage elements called\nregisters.\n\nA typical register-machine instruction applies a\nprimitive operation to the contents of some registers and assigns the\nresult to another register.\n\nOur descriptions of processes executed by\nregister machines will look very much like machine-language\nprograms for traditional computers.\n\nHowever, instead of focusing on\nthe machine language of any particular computer, we will examine\nseveral\nJavaScript\nfunctions\nand design a specific register machine to\nexecute each\nfunction.\n\nThus, we will approach our task from the\nperspective of a hardware architect rather than that of a\nmachine-language computer programmer.\n\nIn designing register machines,\nwe will develop mechanisms for implementing important programming\nconstructs such as recursion.", + "token_count": 294, + "has_code": false, "chapter": "Computing with Register Machines", "section": null, "subsection": null, @@ -1810,8 +1860,8 @@ "chunk_id": "Computing_with_Register_Machines_Computing_with_Register_Machines_1" }, { - "content": "We will also present a language for\ndescribing designs for register machines.\n\nIn\nsection we will\nimplement a\nJavaScript\nprogram that uses these descriptions to simulate the machines we design.\n\nMost of the primitive operations of our register machines are very\nsimple.\n\nFor example, an operation might add the numbers fetched from\ntwo registers, producing a result to be stored into a third register.\n\nSuch an operation can be performed by easily described hardware.\n\nIn\norder to deal with list structure, however, we will also use the\nmemory operations\nhead,\ntail,\nand\npair,\nwhich require an elaborate storage-allocation mechanism.\n\nIn\nsection we study their\nimplementation in terms of more elementary operations.\n\nIn section , after we have accumulated\nexperience formulating simple\nfunctions\nas register machines, we will design a\nmachine that carries out the algorithm described by the metacircular\nevaluator of section.\n\nThis will fill in\nthe gap in our understanding of how\nJavaScript programs\nare interpreted, by providing an explicit model for the mechanisms of\ncontrol in the evaluator.\n\nIn section we will study a simple\ncompiler that translates\nJavaScript\nprograms into sequences of instructions that can be executed directly with\nthe registers and operations of the evaluator register machine.", - "token_count": 201, + "content": "In designing register machines,\nwe will develop mechanisms for implementing important programming\nconstructs such as recursion.\n\nIn\nsection we will\nimplement a\nJavaScript\nprogram that uses these descriptions to simulate the machines we design.\n\nMost of the primitive operations of our register machines are very\nsimple.\n\nFor example, an operation might add the numbers fetched from\ntwo registers, producing a result to be stored into a third register.\n\nSuch an operation can be performed by easily described hardware.\n\nIn\norder to deal with list structure, however, we will also use the\nmemory operations\nhead,\ntail,\nand\npair,\nwhich require an elaborate storage-allocation mechanism.\n\nIn\nsection we study their\nimplementation in terms of more elementary operations.\n\nIn section , after we have accumulated\nexperience formulating simple\nfunctions\nas register machines, we will design a\nmachine that carries out the algorithm described by the metacircular\nevaluator of section.\n\nThis will fill in\nthe gap in our understanding of how\nJavaScript programs\nare interpreted, by providing an explicit model for the mechanisms of\ncontrol in the evaluator.\n\nIn section we will study a simple\ncompiler that translates\nJavaScript\nprograms into sequences of instructions that can be executed directly with\nthe registers and operations of the evaluator register machine.", + "token_count": 205, "has_code": false, "chapter": "Computing with Register Machines", "section": null, @@ -1830,8 +1880,8 @@ "chunk_id": "Computing_with_Register_Machines_A_Register-Machine_Simulator_1" }, { - "content": "The first argument to\nmake_machine\nis a list of register names.\n\nThe next argument is a table (a list of\ntwo-element lists) that pairs each operation name with a\nJavaScript function\nthat implements the operation (that is, produces the same output value\ngiven the same input values).\n\nThe last argument specifies the controller\nas a list of labels and machine instructions, as in\nsection.\n\nTo compute GCDs with this machine, we set the input registers, start the machine, and examine the result when the simulation terminates:\n\n```javascript\nset_register_contents_a\n gcd_machine\n start\n 'done'\n\nset_register_contents(gcd_machine, \"a\", 206);\n```\n\n```javascript\nset_register_contents_b\n set_register_contents_a\n 'done'\n\nset_register_contents(gcd_machine, \"b\", 40);\n```\n\n```javascript\nstart_gcd_machine\n set_register_contents_b\n 'done'\n\nstart(gcd_machine);\n```\n\n```javascript\nget_register_contents_a\n start_gcd_machine\n 2\n\nget_register_contents(gcd_machine, \"a\");\n```\n\nThis computation will run much more slowly than a function written in JavaScript, because we will simulate low-level machine instructions, such as", - "token_count": 138, + "content": "The first argument to\nmake_machine\nis a list of register names.\n\nThe last argument specifies the controller\nas a list of labels and machine instructions, as in\nsection.\n\nTo compute GCDs with this machine, we set the input registers, start the machine, and examine the result when the simulation terminates:\n\n```javascript\nset_register_contents_a\n gcd_machine\n start\n 'done'\n\nset_register_contents(gcd_machine, \"a\", 206);\n\n\"done\"\n```\n\n```javascript\nset_register_contents_b\n set_register_contents_a\n 'done'\n\nset_register_contents(gcd_machine, \"b\", 40);\n\n\"done\"\n```\n\n```javascript\nstart_gcd_machine\n set_register_contents_b\n 'done'\n\nstart(gcd_machine);\n\n\"done\"\n```\n\n```javascript\nget_register_contents_a\n start_gcd_machine\n 2\n\nget_register_contents(gcd_machine, \"a\");\n\n2\n```\n\nThis computation will run much more slowly than a function written in JavaScript, because we will simulate low-level machine instructions, such as , by much\n\nmore complex operations.", + "token_count": 112, "has_code": true, "chapter": "Computing with Register Machines", "section": "A Register-Machine Simulator", @@ -1840,8 +1890,8 @@ "chunk_id": "Computing_with_Register_Machines_A_Register-Machine_Simulator_2" }, { - "content": "The assembler calls\nmake_execution_function\nto generate the execution\nfunction\nfor a controller instruction.\n\nLike the\nfunction\nin the evaluator of section ,\nthis dispatches on the type of instruction to generate the appropriate\nexecution\nfunction.\n\n```javascript\nThe details of these execution functions determine the\n meaning of the individual instructions in the register-machine language.\n```\n\n```javascript\ngcd_machine_complete_example\n\nfunction make_execution_function(inst, labels, machine,\n pc, flag, stack, ops) {\n const inst_type = type(inst);\n return inst_type === \"assign\"\n ? make_assign_ef(inst, machine, labels, ops, pc)\n : inst_type === \"test\"\n ? make_test_ef(inst, machine, labels, ops, flag, pc)\n : inst_type === \"branch\"\n ? make_branch_ef(inst, machine, labels, flag, pc)\n : inst_type === \"go_to\"\n ? make_go_to_ef(inst, machine, labels, pc)\n : inst_type === \"save\"\n ? make_save_ef(inst, machine, stack, pc)\n : inst_type === \"restore\"\n ? make_restore_ef(inst, machine, stack, pc)\n : inst_type === \"perform\"\n ? make_perform_ef(inst, machine, labels, ops, pc)\n : error(inst, \"unknown instruction type -- assemble\");\n}\n```\n\n```javascript\nmake_execution_function\n make_assign\n make_test\n test_instruction_syntax\n make_branch_5\n branch_branch_dest\n make_go_to\n go_to_go_to_dest\n make_save\n save_restore\n make_perform\n perform_perform_action\n gcd_machine_complete_example\n\nfunction make_execution_function(inst, labels, machine,\n pc, flag, stack, ops) {\n return type(inst) === \"assign\"\n ? make_assign_ef(inst, machine, labels, ops, pc)\n : type(inst) === \"test\"\n ? make_test_ef(inst, machine, labels, ops, flag, pc)\n : type(inst) === \"branch\"\n ? make_branch_ef(inst, machine, labels, flag, pc)\n : type(inst) === \"go_to\"\n ? make_go_to_ef(inst, machine, labels, pc)\n : type(inst) === \"save\"\n ? make_save_ef(inst, machine, stack, pc)\n : type(inst) === \"restore\"\n ? make_restore_ef(inst, machine, stack, pc)\n : type(inst) === \"push_marker_to_stack\"\n ? make_push_marker_to_stack_ef(machine, stack, pc)\n : type(inst) === \"revert_stack_to_marker\"\n ? make_revert_stack_to_marker_ef(machine, stack, pc)\n : type(inst) === \"perform\"\n ? make_perform_ef(inst, machine, labels, ops, pc)\n : error(inst, \"unknown instruction type -- assemble\");\n}\n```", - "token_count": 262, + "content": "The assembler calls\nmake_execution_function\nto generate the execution\nfunction\nfor a controller instruction.\n\nLike the\nfunction\nin the evaluator of section ,\nthis dispatches on the type of instruction to generate the appropriate\nexecution\nfunction.\n\nThe details of these execution functions determine the meaning of the individual instructions in the register-machine language.\n\n```javascript\ngcd_machine_complete_example\n\nfunction make_execution_function(inst, labels, machine,\n pc, flag, stack, ops) {\n const inst_type = type(inst);\n return inst_type === \"assign\"\n ? make_assign_ef(inst, machine, labels, ops, pc)\n : inst_type === \"test\"\n ? make_test_ef(inst, machine, labels, ops, flag, pc)\n : inst_type === \"branch\"\n ? make_branch_ef(inst, machine, labels, flag, pc)\n : inst_type === \"go_to\"\n ? make_go_to_ef(inst, machine, labels, pc)\n : inst_type === \"save\"\n ? make_save_ef(inst, machine, stack, pc)\n : inst_type === \"restore\"\n ? make_restore_ef(inst, machine, stack, pc)\n : inst_type === \"perform\"\n ? make_perform_ef(inst, machine, labels, ops, pc)\n : error(inst, \"unknown instruction type -- assemble\");\n}\n```\n\n```javascript\nmake_execution_function\n make_assign\n make_test\n test_instruction_syntax\n make_branch_5\n branch_branch_dest\n make_go_to\n go_to_go_to_dest\n make_save\n save_restore\n make_perform\n perform_perform_action\n gcd_machine_complete_example\n\nfunction make_execution_function(inst, labels, machine,\n pc, flag, stack, ops) {\n return type(inst) === \"assign\"\n ? make_assign_ef(inst, machine, labels, ops, pc)\n : type(inst) === \"test\"\n ? make_test_ef(inst, machine, labels, ops, flag, pc)\n : type(inst) === \"branch\"\n ? make_branch_ef(inst, machine, labels, flag, pc)\n : type(inst) === \"go_to\"\n ? make_go_to_ef(inst, machine, labels, pc)\n : type(inst) === \"save\"\n ? make_save_ef(inst, machine, stack, pc)\n : type(inst) === \"restore\"\n ? make_restore_ef(inst, machine, stack, pc)\n : type(inst) === \"push_marker_to_stack\"\n ? make_push_marker_to_stack_ef(machine, stack, pc)\n : type(inst) === \"revert_stack_to_marker\"\n ? make_revert_stack_to_marker_ef(machine, stack, pc)\n : type(inst) === \"perform\"\n ? make_perform_ef(inst, machine, labels, ops, pc)\n : error(inst, \"unknown instruction type -- assemble\");\n}\n```\n\nThe elements of the controller sequence received by make_machine and passed to assemble are strings (for labels) and tagged lists (for instructions).", + "token_count": 282, "has_code": true, "chapter": "Computing with Register Machines", "section": "A Register-Machine Simulator", @@ -1850,8 +1900,8 @@ "chunk_id": "Computing_with_Register_Machines_Instructions_and_Their_Execution_Functions_1" }, { - "content": "Like the\nfunction\nin the evaluator of section ,\nthis dispatches on the type of instruction to generate the appropriate\nexecution\nfunction.\n\n```javascript\nThe elements of the controller sequence\n received by make_machine and passed\n to assemble are strings (for\n labels) and tagged lists (for instructions). The tag in an instruction\n is a string that identifies the instruction type, such as\n \"go_to\", and the remaining elements\n of the list contains the arguments, such as the destination of the\n go_to.\n The dispatch in make_execution_function uses\n\n\t type_function\n gcd_machine_complete_example\n\nfunction type(instruction) { return head(instruction); }\n```\n\nThe tagged lists are constructed when the\nlist expression that is the third\nargument to make_machine is\nevaluated.\n\nEach argument to that\nlist is either a string (which\nevaluates to itself) or a call to a constructor for an instruction\ntagged list.\n\nFor example, assign(\"b\", reg(\"t\")) calls the constructor\nassign with arguments\n\"b\" and the result of calling the\nconstructor reg with the argument\n\"t\".\n\nThe constructors and their\narguments determine the syntax of the individual instructions in the\nregister-machine language.\n\nThe instruction constructors and selectors\nare shown below, along with the execution-function generators that use\nthe selectors.\n\nThe make_assign_ef function makes execution functions for assign instructions:\n\n```javascript\nmake_assign\n type_function\n make_operation_exp\n assign_reg_name\n gcd_machine_complete_example\n\nfunction make_assign_ef(inst, machine, labels, operations, pc) {\n const target = get_register(machine, assign_reg_name(inst));\n const value_exp = assign_value_exp(inst);\n const value_fun =\n is_operation_exp(value_exp)\n ? make_operation_exp_ef(value_exp, machine, labels, operations)\n : make_primitive_exp_ef(value_exp, machine, labels);\n return () => {\n set_contents(target, value_fun());\n advance_pc(pc);\n };\n}\n```\n\n```javascript\nThe function assign constructs\n\tassign instructions.\n\tThe selectors assign_@reg_@name and\n\tassign_value_exp extract the register name\n\tand value expression from an assign instruction.\n```\n\n```javascript\nassign_reg_name\n\n gcd_machine_complete_example\n\nfunction assign(register_name, source) {\n return list(\"assign\", register_name, source);\n}\nfunction assign_reg_name(assign_instruction) {\n return head(tail(assign_instruction));\n}\nfunction assign_value_exp(assign_instruction) {\n return head(tail(tail(assign_instruction)));\n}\n```", - "token_count": 293, + "content": "The elements of the controller sequence received by make_machine and passed to assemble are strings (for labels) and tagged lists (for instructions).\n\nThe dispatch in make_execution_function uses type_function gcd_machine_complete_example function type(instruction) { return head(instruction); }\n\nThe tagged lists are constructed when the\nlist expression that is the third\nargument to make_machine is\nevaluated.\n\nEach argument to that\nlist is either a string (which\nevaluates to itself) or a call to a constructor for an instruction\ntagged list.\n\nFor example, assign(\"b\", reg(\"t\")) calls the constructor\nassign with arguments\n\"b\" and the result of calling the\nconstructor reg with the argument\n\"t\".\n\nThe constructors and their\narguments determine the syntax of the individual instructions in the\nregister-machine language.\n\nThe instruction constructors and selectors\nare shown below, along with the execution-function generators that use\nthe selectors.\n\nThe make_assign_ef function makes execution functions for assign instructions:\n\n```javascript\nmake_assign\n type_function\n make_operation_exp\n assign_reg_name\n gcd_machine_complete_example\n\nfunction make_assign_ef(inst, machine, labels, operations, pc) {\n const target = get_register(machine, assign_reg_name(inst));\n const value_exp = assign_value_exp(inst);\n const value_fun =\n is_operation_exp(value_exp)\n ? make_operation_exp_ef(value_exp, machine, labels, operations)\n : make_primitive_exp_ef(value_exp, machine, labels);\n return () => {\n set_contents(target, value_fun());\n advance_pc(pc);\n };\n}\n```\n\nThe function assign constructs assign instructions.\n\nThe selectors assign_@reg_@name and assign_value_exp extract the register name and value expression from an assign instruction.\n\n```javascript\nassign_reg_name\n\n gcd_machine_complete_example\n\nfunction assign(register_name, source) {\n return list(\"assign\", register_name, source);\n}\nfunction assign_reg_name(assign_instruction) {\n return head(tail(assign_instruction));\n}\nfunction assign_value_exp(assign_instruction) {\n return head(tail(tail(assign_instruction)));\n}\n```\n\nThe function make_assign_ef looks up the register name\nwith\nget_register\nto produce the target register object.\n\nThe value expression is passed to\nmake_@operation_@exp_@ef\nif the value is the result of an operation, and\nit is passed\nto\nmake_@primitive_@exp_@ef\notherwise.\n\nThese\nfunctions\n(shown below)\nanalyze\nthe value expression and produce an execution\nfunction\nfor the value.", + "token_count": 289, "has_code": true, "chapter": "Computing with Register Machines", "section": "A Register-Machine Simulator", @@ -1860,8 +1910,8 @@ "chunk_id": "Computing_with_Register_Machines_Instructions_and_Their_Execution_Functions_2" }, { - "content": "The make_assign_ef function makes execution functions for assign instructions:\n\nThe function make_assign_ef looks up the register name\nwith\nget_register\nto produce the target register object.\n\nThe value expression is passed to\nmake_@operation_@exp_@ef\nif the value is the result of an operation, and\nit is passed\nto\nmake_@primitive_@exp_@ef\notherwise.\n\nThese\nfunctions\n(shown below)\nanalyze\nthe value expression and produce an execution\nfunction\nfor the value.\n\nThis is a\nfunction\nof no arguments, called\nvalue_fun,\nwhich will be evaluated during the simulation to produce the actual\nvalue to be assigned to the register.\n\nNotice that the work of looking\nup the register name and\nanalyzing\nthe value expression is performed\njust once, at assembly time, not every time the instruction is\nsimulated.\n\nThis saving of work is the reason we use execution\nfunctions,\nand corresponds directly to the saving in work we obtained by separating\nprogram analysis from execution in the evaluator of\nsection.\n\nThe result returned by\nmake_assign_ef\nis the execution\nfunction\nfor the\nfunction\nis called (by the machine model s\nfunction),\nit sets the contents of the target register to the result obtained by\nexecuting\nvalue_fun.\n\nThen it advances the\nfunction\n\n```javascript\nadvance_pc\n gcd_machine_complete_example\n\nfunction advance_pc(pc) {\n set_contents(pc, tail(get_contents(pc)));\n}\n```\n\n```javascript\nThe function\n\tadvance_pc\n```\n\nis the normal termination for all instructions except go_to.\n\n```javascript\nThe function\n\tmake_test_ef\n```\n\nhandles\nfunction\nfor it.\n\nAt simulation time, the\nfunction\nfor the condition is called, the result is assigned to the\n\n```javascript\nmake_test\n advance_pc\n gcd_machine_complete_example\n\nfunction make_test_ef(inst, machine, labels, operations, flag, pc) {\n const condition = test_condition(inst);\n if (is_operation_exp(condition)) {\n const condition_fun = make_operation_exp_ef(\n condition, machine,\n labels, operations);\n return () => {\n set_contents(flag, condition_fun());\n advance_pc(pc);\n };\n } else {\n error(inst, \"bad test instruction -- assemble\");\n }\n}\n```", - "token_count": 288, + "content": "These\nfunctions\n(shown below)\nanalyze\nthe value expression and produce an execution\nfunction\nfor the value.\n\nNotice that the work of looking\nup the register name and\nanalyzing\nthe value expression is performed\njust once, at assembly time, not every time the instruction is\nsimulated.\n\nThis saving of work is the reason we use execution\nfunctions,\nand corresponds directly to the saving in work we obtained by separating\nprogram analysis from execution in the evaluator of\nsection.\n\nThe result returned by\nmake_assign_ef\nis the execution\nfunction\nfor the instruction.\n\nWhen this\nfunction\nis called (by the machine model s\nfunction),\nit sets the contents of the target register to the result obtained by\nexecuting\nvalue_fun.\n\nThen it advances the to the next instruction\nby running the\nfunction\n\n```javascript\nadvance_pc\n gcd_machine_complete_example\n\nfunction advance_pc(pc) {\n set_contents(pc, tail(get_contents(pc)));\n}\n```\n\nThe function advance_pc is the normal termination for all instructions except and go_to.\n\nThe function make_test_ef\nhandles instructions in a similar way.\n\nIt extracts the expression that specifies the condition to be tested and\ngenerates an execution\nfunction\nfor it.\n\nAt simulation time, the\nfunction\nfor the condition is called, the result is assigned to the\nregister, and the\nis advanced:\n\n```javascript\nmake_test\n advance_pc\n gcd_machine_complete_example\n\nfunction make_test_ef(inst, machine, labels, operations, flag, pc) {\n const condition = test_condition(inst);\n if (is_operation_exp(condition)) {\n const condition_fun = make_operation_exp_ef(\n condition, machine,\n labels, operations);\n return () => {\n set_contents(flag, condition_fun());\n advance_pc(pc);\n };\n } else {\n error(inst, \"bad test instruction -- assemble\");\n }\n}\n```\n\nThe function test constructs test instructions.\n\nThe selector test_condition extracts the condition from a test. test_instruction_syntax gcd_machine_complete_example function test(condition) { return list(\"test\", condition); } function test_condition(test_instruction) { return head(tail(test_instruction)); }", + "token_count": 275, "has_code": true, "chapter": "Computing with Register Machines", "section": "A Register-Machine Simulator", @@ -1870,8 +1920,8 @@ "chunk_id": "Computing_with_Register_Machines_Instructions_and_Their_Execution_Functions_3" }, { - "content": "At simulation time, the\nfunction\nfor the condition is called, the result is assigned to the\n\n```javascript\nThe function\n\ttest constructs\n\ttest instructions. The selector\n\ttest_condition extracts the condition\n\tfrom a test.\n\n\t test_instruction_syntax\n gcd_machine_complete_example\n\nfunction test(condition) { return list(\"test\", condition); }\n\nfunction test_condition(test_instruction) {\n return head(tail(test_instruction));\n}\n```\n\nThe execution\nfunction\nfor a\nmake_branch_ef\nfunction\nenforces this.\n\nNotice also that the label is looked up at assembly time,\nnot each time the\n\n```javascript\nmake_branch_5\n gcd_machine_complete_example\n\nfunction make_branch_ef(inst, machine, labels, flag, pc) {\n const dest = branch_dest(inst);\n if (is_label_exp(dest)) {\n const insts = lookup_label(labels, label_exp_label(dest));\n return () => {\n if (get_contents(flag)) {\n set_contents(pc, insts);\n } else {\n advance_pc(pc);\n }\n };\n } else {\n error(inst, \"bad branch instruction -- assemble\");\n }\n}\n```\n\n```javascript\nThe function branch\n\tconstructs branch instructions. The\n\tselector\n\tbranch_dest extracts\n\tthe destination from a branch.\n\n\t branch_branch_dest\n gcd_machine_complete_example\n\nfunction branch(label) { return list(\"branch\", label); }\n\nfunction branch_dest(branch_instruction) {\n return head(tail(branch_instruction));\n}\n```\n\nA go_to instruction is similar to a branch, except that the destination may be specified either as a label or as a register, and there\n\nis no condition to check the\n\n```javascript\nmake_go_to\n is_register_exp\n gcd_machine_complete_example\n\nfunction make_go_to_ef(inst, machine, labels, pc) {\n const dest = go_to_dest(inst);\n if (is_label_exp(dest)) {\n const insts = lookup_label(labels, label_exp_label(dest));\n return () => set_contents(pc, insts);\n } else if (is_register_exp(dest)) {\n const reg = get_register(machine, register_exp_reg(dest));\n return () => set_contents(pc, get_contents(reg));\n } else {\n error(inst, \"bad go_to instruction -- assemble\");\n }\n}\n```\n\n```javascript\nThe function go_to constructs\n\tgo_to instructions. The selector\n\tgo_to_dest extracts the destination from a\n\tgo_to instruction.\n\n\t go_to_go_to_dest\n make_go_to\n gcd_machine_complete_example\n\nfunction go_to(label) { return list(\"go_to\", label); }\n\nfunction go_to_dest(go_to_instruction) {\n return head(tail(go_to_instruction));\n}\n```\n\nThe stack instructions", - "token_count": 275, + "content": "The selector test_condition extracts the condition from a test. test_instruction_syntax gcd_machine_complete_example function test(condition) { return list(\"test\", condition); } function test_condition(test_instruction) { return head(tail(test_instruction)); }\n\nNotice that the indicated destination in a\ninstruction must be a label, and the\nmake_branch_ef\nfunction\nenforces this.\n\nNotice also that the label is looked up at assembly time,\nnot each time the instruction is\nsimulated.\n\n```javascript\nmake_branch_5\n gcd_machine_complete_example\n\nfunction make_branch_ef(inst, machine, labels, flag, pc) {\n const dest = branch_dest(inst);\n if (is_label_exp(dest)) {\n const insts = lookup_label(labels, label_exp_label(dest));\n return () => {\n if (get_contents(flag)) {\n set_contents(pc, insts);\n } else {\n advance_pc(pc);\n }\n };\n } else {\n error(inst, \"bad branch instruction -- assemble\");\n }\n}\n```\n\nThe function branch constructs branch instructions.\n\nThe selector branch_dest extracts the destination from a branch. branch_branch_dest gcd_machine_complete_example function branch(label) { return list(\"branch\", label); } function branch_dest(branch_instruction) { return head(tail(branch_instruction)); }\n\nA go_to instruction is similar to a branch, except that the destination may be specified either as a label or as a register, and there\n\nis no condition to check the is always set to the new destination.\n\n```javascript\nmake_go_to\n is_register_exp\n gcd_machine_complete_example\n\nfunction make_go_to_ef(inst, machine, labels, pc) {\n const dest = go_to_dest(inst);\n if (is_label_exp(dest)) {\n const insts = lookup_label(labels, label_exp_label(dest));\n return () => set_contents(pc, insts);\n } else if (is_register_exp(dest)) {\n const reg = get_register(machine, register_exp_reg(dest));\n return () => set_contents(pc, get_contents(reg));\n } else {\n error(inst, \"bad go_to instruction -- assemble\");\n }\n}\n```\n\nThe function go_to constructs go_to instructions.\n\nThe selector go_to_dest extracts the destination from a go_to instruction. go_to_go_to_dest make_go_to gcd_machine_complete_example function go_to(label) { return list(\"go_to\", label); } function go_to_dest(go_to_instruction) { return head(tail(go_to_instruction)); }\n\nThe stack instructions and simply use the stack with the designated register and advance the :", + "token_count": 279, "has_code": true, "chapter": "Computing with Register Machines", "section": "A Register-Machine Simulator", @@ -1880,8 +1930,8 @@ "chunk_id": "Computing_with_Register_Machines_Instructions_and_Their_Execution_Functions_4" }, { - "content": "The stack instructions\n\n```javascript\nmake_save\n pop\n gcd_machine_complete_example\n\nfunction make_save_ef(inst, machine, stack, pc) {\n const reg = get_register(machine, stack_inst_reg_name(inst));\n return () => {\n push(stack, get_contents(reg));\n advance_pc(pc);\n };\n}\nfunction make_restore_ef(inst, machine, stack, pc) {\n const reg = get_register(machine, stack_inst_reg_name(inst));\n return () => {\n set_contents(reg, pop(stack));\n advance_pc(pc);\n };\n}\n```\n\n```javascript\nThe functions save and\n\trestore construct\n\tsave and restore instructions. The\n\tselector\n\tstack_inst_reg_name\n\textracts the register name from such instructions.\n\n\t save_restore\n\t make_save\n\t gcd_machine_complete_example\n\nfunction save(reg) { return list(\"save\", reg); }\n\nfunction restore(reg) { return list(\"restore\", reg); }\n\nfunction stack_inst_reg_name(stack_instruction) {\n return head(tail(stack_instruction));\n}\n```\n\nThe final instruction type, handled by\nmake_perform_ef,\ngenerates an execution\nfunction\nfor the action to be performed.\n\nAt simulation time, the action\nfunction\nis executed and the\n\n```javascript\nmake_perform\n is_register_exp\n gcd_machine_complete_example\n\nfunction make_perform_ef(inst, machine, labels, operations, pc) {\n const action = perform_action(inst);\n if (is_operation_exp(action)) {\n const action_fun = make_operation_exp_ef(action, machine,\n labels, operations);\n return () => {\n action_fun();\n advance_pc(pc);\n };\n } else {\n error(inst, \"bad perform instruction -- assemble\");\n }\n}\n```\n\n```javascript\nThe function perform\n\tconstructs perform instructions. The\n\tselector\n\tperform_@action extracts\n\tthe action from a perform instruction.\n\n\t perform_perform_action\n make_perform\n gcd_machine_complete_example\n\nfunction perform(action) { return list(\"perform\", action); }\n\nfunction perform_action(perform_instruction) {\n return head(tail(perform_instruction));\n}\n```\n\nThe value of a\nconstant\nexpression may be needed for assignment to a register\n(make_assign_ef, above)\nor for input to an operation\n(make_@operation_@exp_@ef,\nbelow).\n\nThe following\nfunction\ngenerates execution\nfunctions\nto produce values for these expressions during the simulation:\n\n```javascript\nmake_primitive_exp\n lookup_label\n gcd_machine_complete_example\n\nfunction make_primitive_exp_ef(exp, machine, labels) {\n if (is_constant_exp(exp)) {\n const c = constant_exp_value(exp);\n return () => c;\n } else if (is_label_exp(exp)) {\n const insts = lookup_label(labels, label_exp_label(exp));\n return () => insts;\n } else if (is_register_exp(exp)) {\n const r = get_register(machine, register_exp_reg(exp));\n return () => get_contents(r);\n } else {\n error(exp, \"unknown expression type -- assemble\");\n }\n}\n```", - "token_count": 297, + "content": "The stack instructions and simply use the stack with the designated register and advance the :\n\nThe functions save and restore construct save and restore instructions.\n\nThe selector stack_inst_reg_name extracts the register name from such instructions. save_restore make_save gcd_machine_complete_example function save(reg) { return list(\"save\", reg); } function restore(reg) { return list(\"restore\", reg); } function stack_inst_reg_name(stack_instruction) { return head(tail(stack_instruction)); }\n\nThe final instruction type, handled by\nmake_perform_ef,\ngenerates an execution\nfunction\nfor the action to be performed.\n\nAt simulation time, the action\nfunction\nis executed and the advanced.\n\n```javascript\nmake_perform\n is_register_exp\n gcd_machine_complete_example\n\nfunction make_perform_ef(inst, machine, labels, operations, pc) {\n const action = perform_action(inst);\n if (is_operation_exp(action)) {\n const action_fun = make_operation_exp_ef(action, machine,\n labels, operations);\n return () => {\n action_fun();\n advance_pc(pc);\n };\n } else {\n error(inst, \"bad perform instruction -- assemble\");\n }\n}\n```\n\nThe function perform constructs perform instructions.\n\nThe selector perform_@action extracts the action from a perform instruction. perform_perform_action make_perform gcd_machine_complete_example function perform(action) { return list(\"perform\", action); } function perform_action(perform_instruction) { return head(tail(perform_instruction)); }\n\nThe value of a\n,\n, or\nconstant\nexpression may be needed for assignment to a register\n(make_assign_ef, above)\nor for input to an operation\n(make_@operation_@exp_@ef,\nbelow).\n\nThe following\nfunction\ngenerates execution\nfunctions\nto produce values for these expressions during the simulation:\n\n```javascript\nmake_primitive_exp\n lookup_label\n gcd_machine_complete_example\n\nfunction make_primitive_exp_ef(exp, machine, labels) {\n if (is_constant_exp(exp)) {\n const c = constant_exp_value(exp);\n return () => c;\n } else if (is_label_exp(exp)) {\n const insts = lookup_label(labels, label_exp_label(exp));\n return () => insts;\n } else if (is_register_exp(exp)) {\n const r = get_register(machine, register_exp_reg(exp));\n return () => get_contents(r);\n } else {\n error(exp, \"unknown expression type -- assemble\");\n }\n}\n```\n\nThe syntax of reg, label, and constant expressions is determined by the following constructor functions, along with corresponding predicates and selectors. is_register_exp0 tagged_list gcd_machine_complete_example function", + "token_count": 290, "has_code": true, "chapter": "Computing with Register Machines", "section": "A Register-Machine Simulator", @@ -1890,8 +1940,8 @@ "chunk_id": "Computing_with_Register_Machines_Instructions_and_Their_Execution_Functions_5" }, { - "content": "The following\nfunction\ngenerates execution\nfunctions\nto produce values for these expressions during the simulation:\n\n```javascript\nThe syntax of reg,\n\tlabel, and constant\n expressions is determined by the following constructor functions, along with\n corresponding predicates and selectors.\n\n\t is_register_exp0\n\t tagged_list\n\t gcd_machine_complete_example\n\nfunction reg(name) { return list(\"reg\", name); }\n\nfunction is_register_exp(exp) { return is_tagged_list(exp, \"reg\"); }\n\nfunction register_exp_reg(exp) { return head(tail(exp)); }\n\nfunction constant(value) { return list(\"constant\", value); }\n\nfunction is_constant_exp(exp) {\n return is_tagged_list(exp, \"constant\");\n}\n\nfunction constant_exp_value(exp) { return head(tail(exp)); }\n\nfunction label(name) { return list(\"label\", name); }\n\nfunction is_label_exp(exp) { return is_tagged_list(exp, \"label\"); }\n\nfunction label_exp_label(exp) { return head(tail(exp)); }\n```\n\nThe instructions\nmay include the application of a machine operation (specified by an\nconstant\nexpressions).\n\nThe following\nfunction\nproduces an execution\nfunction\nfor an operation expression a list containing the\noperation and operand expressions from the instruction:\n\n```javascript\nmake_operation_exp\n lookup_prim\n make_primitive_exp\n gcd_machine_complete_example\n\nfunction make_operation_exp_ef(exp, machine, labels, operations) {\n const op = lookup_prim(operation_exp_op(exp), operations);\n const afuns = map(e => make_primitive_exp_ef(e, machine, labels),\n operation_exp_operands(exp));\n return () => apply_in_underlying_javascript(\n op, map(f => f(), afuns));\n}\n```\n\n```javascript\nThe syntax of operation expressions is determined by\n\n\t is_register_exp\n\t is_register_exp0\n\t gcd_machine_complete_example\n\nfunction op(name) { return list(\"op\", name); }\n\nfunction is_operation_exp(exp) {\n return is_pair(exp) && is_tagged_list(head(exp), \"op\");\n}\n\nfunction operation_exp_op(op_exp) { return head(tail(head(op_exp))); }\n\nfunction operation_exp_operands(op_exp) { return tail(op_exp); }\n```\n\nObserve that the treatment of operation expressions is very much like the treatment of function applications by the analyze_application function in the evaluator of section\n\nin that we generate an execution function for each operand.\n\n```javascript\nAt simulation time, we call the operand\n\tfunctions\n\tand apply the JavaScript function\n```\n\nthat simulates the operation to the resulting values.", - "token_count": 273, + "content": "The syntax of reg, label, and constant expressions is determined by the following constructor functions, along with corresponding predicates and selectors. is_register_exp0 tagged_list gcd_machine_complete_example function\n\n} function is_constant_exp(exp) { return is_tagged_list(exp, \"constant\"); } function constant_exp_value(exp) { return head(tail(exp)); } function label(name) { return list(\"label\", name); } function is_label_exp(exp) { return\n\nis_tagged_list(exp, \"label\"); } function label_exp_label(exp) { return head(tail(exp)); }\n\nThe instructions , , and\nmay include the application of a machine operation (specified by an\nexpression) to some operands (specified\nby and\nconstant\nexpressions).\n\nThe following\nfunction\nproduces an execution\nfunction\nfor an operation expression a list containing the\noperation and operand expressions from the instruction:\n\n```javascript\nmake_operation_exp\n lookup_prim\n make_primitive_exp\n gcd_machine_complete_example\n\nfunction make_operation_exp_ef(exp, machine, labels, operations) {\n const op = lookup_prim(operation_exp_op(exp), operations);\n const afuns = map(e => make_primitive_exp_ef(e, machine, labels),\n operation_exp_operands(exp));\n return () => apply_in_underlying_javascript(\n op, map(f => f(), afuns));\n}\n```\n\nThe syntax of operation expressions is determined by is_register_exp is_register_exp0 gcd_machine_complete_example function op(name) { return list(\"op\", name); } function is_operation_exp(exp) { return is_pair(exp) && is_tagged_list(head(exp), \"op\"); } function operation_exp_op(op_exp) { return head(tail(head(op_exp))); } function operation_exp_operands(op_exp) { return tail(op_exp); }\nObserve that the treatment of operation expressions is very much like\nthe treatment of\nfunction\napplications by the\nanalyze_application\nfunction\nin the evaluator of section in\nthat we generate an execution\nfunction\nfor each operand.\n\nAt simulation time, we call the operand functions and apply the JavaScript function\nthat simulates the operation to the resulting values.\n\nWe make use of the function apply_in_underlying_javascript, as we did in apply_primitive_function in section.\n\nThis is needed to apply op to all elements of the argument list afuns produced by the first map, as if they were separate arguments to op.\n\nWithout this, op would have been restricted to be a unary function.", + "token_count": 290, "has_code": true, "chapter": "Computing with Register Machines", "section": "A Register-Machine Simulator", @@ -1900,8 +1950,8 @@ "chunk_id": "Computing_with_Register_Machines_Instructions_and_Their_Execution_Functions_6" }, { - "content": "that simulates the operation to the resulting values.\n\n```javascript\nWe make use of the function\n\tapply_in_underlying_javascript, as we did\n\tin apply_primitive_function in\n\tsection. This is needed to apply\n\top to all elements of the argument list\n\tafuns\n\tproduced by the first map,\n\tas if they were separate arguments to\n\top. Without this,\n\top would have been restricted to be a unary\n\tfunction.\n```\n\nThe simulation function is found by looking up the operation name in the operation table for the machine:\n\n```javascript\nlookup_prim\n gcd_machine_complete_example\n\nfunction lookup_prim(symbol, operations) {\n const val = assoc(symbol, operations);\n return is_undefined(val)\n ? error(symbol, \"unknown operation -- assemble\")\n : head(tail(val));\n}\n```", - "token_count": 105, + "content": "Without this, op would have been restricted to be a unary function.\n\n```javascript\nlookup_prim\n gcd_machine_complete_example\n\nfunction lookup_prim(symbol, operations) {\n const val = assoc(symbol, operations);\n return is_undefined(val)\n ? error(symbol, \"unknown operation -- assemble\")\n : head(tail(val));\n}\n```", + "token_count": 36, "has_code": true, "chapter": "Computing with Register Machines", "section": "A Register-Machine Simulator", @@ -1910,8 +1960,8 @@ "chunk_id": "Computing_with_Register_Machines_Instructions_and_Their_Execution_Functions_7" }, { - "content": "Simulation is useful not only for verifying the correctness of a\nproposed machine design but also for measuring the machine s\nmeter that measures the number of stack operations used in a\ncomputation.\n\nTo do this, we modify our simulated stack to keep track\nof the number of times registers are saved on the stack and the\nmaximum depth reached by the stack, and add a message to the stack s\ninterface that prints the statistics, as shown below.\n\nWe also add an operation to the basic machine model to print the\nstack statistics, by initializing\nthe_ops\nin\nmake_new_machine\nto\n\n```javascript\nlist(list(\"initialize_stack\",\n () => stack(\"initialize\")),\n list(\"print_stack_statistics\",\n () => stack(\"print_statistics\")));\n```\n\nHere is the new version of make_stack:\n\n```javascript\nfunction make_stack() {\n let stack = null;\n let number_pushes = 0;\n let max_depth = 0;\n let current_depth = 0;\n function push(x) {\n stack = pair(x, stack);\n number_pushes = number_pushes + 1;\n current_depth = current_depth + 1;\n max_depth = math_max(current_depth, max_depth);\n return \"done\";\n }\n function pop() {\n if (is_null(stack)) {\n error(\"empty stack -- pop\");\n } else {\n const top = head(stack);\n stack = tail(stack);\n current_depth = current_depth - 1;\n return top;\n }\n }\n function initialize() {\n stack = null;\n number_pushes = 0;\n max_depth = 0;\n current_depth = 0;\n return \"done\";\n }\n function print_statistics() {\n display(\"total pushes = \" + stringify(number_pushes));\n display(\"maximum depth = \" + stringify(max_depth));\n }\n function dispatch(message) {\n return message === \"push\"\n ? push\n : message === \"pop\"\n ? pop()\n : message === \"initialize\"\n ? initialize()\n : message === \"print_statistics\"\n ? print_statistics()\n : error(message, \"unknown request -- stack\");\n }\n return dispatch;\n}\n```\n\nExercises through describe other useful monitoring and debugging features that can be added to the register-machine simulator.", - "token_count": 279, + "content": "Simulation is useful not only for verifying the correctness of a\nproposed machine design but also for measuring the machine s\nperformance.\n\nFor example, we can install in our simulation program a\nmeter that measures the number of stack operations used in a\ncomputation.\n\nTo do this, we modify our simulated stack to keep track\nof the number of times registers are saved on the stack and the\nmaximum depth reached by the stack, and add a message to the stack s\ninterface that prints the statistics, as shown below.\n\nWe also add an operation to the basic machine model to print the\nstack statistics, by initializing\nthe_ops\nin\nmake_new_machine\nto\n\n```javascript\nlist(list(\"initialize_stack\",\n () => stack(\"initialize\")),\n list(\"print_stack_statistics\",\n () => stack(\"print_statistics\")));\n```\n\nHere is the new version of make_stack:\n\n```javascript\nfunction make_stack() {\n let stack = null;\n let number_pushes = 0;\n let max_depth = 0;\n let current_depth = 0;\n function push(x) {\n stack = pair(x, stack);\n number_pushes = number_pushes + 1;\n current_depth = current_depth + 1;\n max_depth = math_max(current_depth, max_depth);\n return \"done\";\n }\n function pop() {\n if (is_null(stack)) {\n error(\"empty stack -- pop\");\n } else {\n const top = head(stack);\n stack = tail(stack);\n current_depth = current_depth - 1;\n return top;\n }\n }\n function initialize() {\n stack = null;\n number_pushes = 0;\n max_depth = 0;\n current_depth = 0;\n return \"done\";\n }\n function print_statistics() {\n display(\"total pushes = \" + stringify(number_pushes));\n display(\"maximum depth = \" + stringify(max_depth));\n }\n function dispatch(message) {\n return message === \"push\"\n ? push\n : message === \"pop\"\n ? pop()\n : message === \"initialize\"\n ? initialize()\n : message === \"print_statistics\"\n ? print_statistics()\n : error(message, \"unknown request -- stack\");\n }\n return dispatch;\n}\n```\n\nExercises through describe other useful monitoring and debugging features that can be added to the register-machine simulator.", + "token_count": 290, "has_code": true, "chapter": "Computing with Register Machines", "section": "A Register-Machine Simulator", @@ -1920,8 +1970,8 @@ "chunk_id": "Computing_with_Register_Machines_Monitoring_Machine_Performance_1" }, { - "content": "The technique of producing an execution\nfunction\nfor each instruction is just what we used in\nsection to speed\nup the evaluator by separating analysis from runtime execution.\n\nAs we\nsaw in chapter , much useful\nJavaScript\nexpressions could\nbe performed without knowing the actual values of\nnames.\n\nHere, analogously, much useful analysis of register-machine-language\nexpressions can be performed without knowing the actual contents of\nmachine registers.\n\nFor example, we can replace references to\nregisters by pointers to the register objects, and we can\nreplace references to labels by pointers to the place in the\ninstruction sequence that the label designates.\n\nBefore it can generate the instruction execution\nfunctions,\nthe assembler must know what all the labels refer to, so it begins by\nscanning the controller sequence to separate the labels from the\ninstructions.\n\nAs it scans the controller , it constructs both a list of\ninstructions and a table that associates each label with a pointer\ninto that list.\n\nThen the assembler augments the instruction list by\ninserting the execution\nfunction\nfor each instruction.\n\nThe\nfunction\nis the main entry to the assembler.\n\nIt takes the controller\nsequence and the\nmachine model as arguments and returns the instruction sequence to be stored\nin the model.\n\n```javascript\nThe function\n\tassemble\n```\n\ncalls\nextract_labels\nto build the initial instruction list and label table from the supplied\ncontroller.\n\nThe second argument\nto\nextract_labels\nis a\nfunction\nto be called to process these results: This\nfunction\nuses\nupdate_insts\nto generate the instruction execution\nfunctions\nand insert them into the instruction list, and returns the modified list.\n\n```javascript\nassemble\n update_insts\n extract_labels\n gcd_machine_complete_example\n\nfunction assemble(controller, machine) {\n return extract_labels(controller,\n (insts, labels) => {\n update_insts(insts, labels, machine);\n return insts;\n });\n}\n```", - "token_count": 286, + "content": "The technique of producing an execution\nfunction\nfor each instruction is just what we used in\nsection to speed\nup the evaluator by separating analysis from runtime execution.\n\nAs we\nsaw in chapter , much useful\nanalysis of\nJavaScript\nexpressions could\nbe performed without knowing the actual values of\nnames.\n\nHere, analogously, much useful analysis of register-machine-language\nexpressions can be performed without knowing the actual contents of\nmachine registers.\n\nFor example, we can replace references to\nregisters by pointers to the register objects, and we can\nreplace references to labels by pointers to the place in the\ninstruction sequence that the label designates.\n\nBefore it can generate the instruction execution\nfunctions,\nthe assembler must know what all the labels refer to, so it begins by\nscanning the controller sequence to separate the labels from the\ninstructions.\n\nAs it scans the controller , it constructs both a list of\ninstructions and a table that associates each label with a pointer\ninto that list.\n\nThen the assembler augments the instruction list by\ninserting the execution\nfunction\nfor each instruction.\n\nThe\nfunction\nis the main entry to the assembler.\n\nIt takes the controller\nsequence and the\nmachine model as arguments and returns the instruction sequence to be stored\nin the model.\n\nThe function assemble\ncalls\nextract_labels\nto build the initial instruction list and label table from the supplied\ncontroller.\n\nThe second argument\nto\nextract_labels\nis a\nfunction\nto be called to process these results: This\nfunction\nuses\nupdate_insts\nto generate the instruction execution\nfunctions\nand insert them into the instruction list, and returns the modified list.\n\n```javascript\nassemble\n update_insts\n extract_labels\n gcd_machine_complete_example\n\nfunction assemble(controller, machine) {\n return extract_labels(controller,\n (insts, labels) => {\n update_insts(insts, labels, machine);\n return insts;\n });\n}\n```\n\nThe function extract_labels takes a list controller and a function receive as arguments.", + "token_count": 299, "has_code": true, "chapter": "Computing with Register Machines", "section": "A Register-Machine Simulator", @@ -1930,8 +1980,8 @@ "chunk_id": "Computing_with_Register_Machines_The_Assembler_1" }, { - "content": "The second argument\nto\nextract_labels\nis a\nfunction\nto be called to process these results: This\nfunction\nuses\nupdate_insts\nto generate the instruction execution\nfunctions\nand insert them into the instruction list, and returns the modified list.\n\n```javascript\nThe function extract_labels takes\n\t a list controller\n\t and a function receive\n\t as arguments. The function receive will\n\t be called with two values: (1)a list insts\n\t of instruction data structures, each containing an instruction from\n\t controller; and (2)a table called\n\t labels, which associates each label from\n\t controller with the position in the list\n\t insts that the label designates.\n```\n\n```javascript\nextract_labels\n make_label_entry\n gcd_machine_complete_example\n\nfunction extract_labels(controller, receive) {\n return is_null(controller)\n ? receive(null, null)\n : extract_labels(\n tail(controller),\n (insts, labels) => {\n const next_element = head(controller);\n return is_string(next_element)\n ? receive(insts,\n pair(make_label_entry(next_element,\n insts),\n labels))\n : receive(pair(make_inst(next_element),\n insts),\n labels);\n });\n}\n```\n\n```javascript\nThe function\n extract_labels\n```\n\nworks by sequentially scanning the elements of the controller and accumulating the string (and thus a label) an appropriate entry is added to the\n\n```javascript\nThe function\n update_insts\n```\n\nmodifies the instruction list, which initially contains only the controller instructions, to include the corresponding execution functions:\n\n```javascript\nupdate_insts\n get_register\n make_execution_function\n make_inst\n gcd_machine_complete_example\n\nfunction update_insts(insts, labels, machine) {\n const pc = get_register(machine, \"pc\");\n const flag = get_register(machine, \"flag\");\n const stack = machine(\"stack\");\n const ops = machine(\"operations\");\n return for_each(inst => set_inst_execution_fun(\n inst,\n make_execution_function(\n inst_controller_instruction(inst),\n labels, machine, pc,\n flag, stack, ops)),\n insts);\n}\n```\n\nThe machine instruction data structure simply pairs the\ncontroller\ninstruction with the corresponding execution\nfunction.\n\nThe execution\nfunction\nis not yet available when\nextract_labels\nconstructs the instruction, and is inserted later by\nupdate_insts.\n\n```javascript\nmake_inst\n gcd_machine_complete_example\n\nfunction make_inst(inst_controller_instruction) {\n return pair(inst_controller_instruction, null);\n}\nfunction inst_controller_instruction(inst) {\n return head(inst);\n}\nfunction inst_execution_fun(inst) {\n return tail(inst);\n}\nfunction set_inst_execution_fun(inst, fun) {\n set_tail(inst, fun);\n}\n```", - "token_count": 293, + "content": "The function extract_labels takes a list controller and a function receive as arguments.\n\n```javascript\nextract_labels\n make_label_entry\n gcd_machine_complete_example\n\nfunction extract_labels(controller, receive) {\n return is_null(controller)\n ? receive(null, null)\n : extract_labels(\n tail(controller),\n (insts, labels) => {\n const next_element = head(controller);\n return is_string(next_element)\n ? receive(insts,\n pair(make_label_entry(next_element,\n insts),\n labels))\n : receive(pair(make_inst(next_element),\n insts),\n labels);\n });\n}\n```\n\nThe function extract_labels\nworks by sequentially scanning the elements of the\ncontroller\nand accumulating the\nand the.\n\nIf an element is a\nstring\n(and thus a label) an appropriate entry is added to the\ntable.\n\nOtherwise the element is\naccumulated onto the\nlist.\n\nThe function update_insts modifies the instruction list, which initially contains only the controller instructions, to include the corresponding execution functions:\n\n```javascript\nupdate_insts\n get_register\n make_execution_function\n make_inst\n gcd_machine_complete_example\n\nfunction update_insts(insts, labels, machine) {\n const pc = get_register(machine, \"pc\");\n const flag = get_register(machine, \"flag\");\n const stack = machine(\"stack\");\n const ops = machine(\"operations\");\n return for_each(inst => set_inst_execution_fun(\n inst,\n make_execution_function(\n inst_controller_instruction(inst),\n labels, machine, pc,\n flag, stack, ops)),\n insts);\n}\n```\n\nThe machine instruction data structure simply pairs the\ncontroller\ninstruction with the corresponding execution\nfunction.\n\nThe execution\nfunction\nis not yet available when\nextract_labels\nconstructs the instruction, and is inserted later by\nupdate_insts.\n\n```javascript\nmake_inst\n gcd_machine_complete_example\n\nfunction make_inst(inst_controller_instruction) {\n return pair(inst_controller_instruction, null);\n}\nfunction inst_controller_instruction(inst) {\n return head(inst);\n}\nfunction inst_execution_fun(inst) {\n return tail(inst);\n}\nfunction set_inst_execution_fun(inst, fun) {\n set_tail(inst, fun);\n}\n```\n\nThe controller instruction is not used by our simulator, but is handy to keep around for debugging (see exercise ).\n\nElements of the label table are pairs:\n\n```javascript\nmake_label_entry\n gcd_machine_complete_example\n\nfunction make_label_entry(label_name, insts) {\n return pair(label_name, insts);\n}\n```\n\nEntries will be looked up in the table with\n\n```javascript\nlookup_label\n gcd_machine_complete_example\n\nfunction lookup_label(labels, label_name) {\n const val = assoc(label_name, labels);\n return is_undefined(val)\n ? error(label_name, \"undefined label -- assemble\")\n : tail(val);\n}\n```", + "token_count": 294, "has_code": true, "chapter": "Computing with Register Machines", "section": "A Register-Machine Simulator", @@ -1940,18 +1990,8 @@ "chunk_id": "Computing_with_Register_Machines_The_Assembler_2" }, { - "content": "The execution\nfunction\nis not yet available when\nextract_labels\nconstructs the instruction, and is inserted later by\nupdate_insts.\n\nThe controller instruction is not used by our simulator, but is handy to keep around for debugging (see exercise ).\n\nElements of the label table are pairs:\n\n```javascript\nmake_label_entry\n gcd_machine_complete_example\n\nfunction make_label_entry(label_name, insts) {\n return pair(label_name, insts);\n}\n```\n\nEntries will be looked up in the table with\n\n```javascript\nlookup_label\n gcd_machine_complete_example\n\nfunction lookup_label(labels, label_name) {\n const val = assoc(label_name, labels);\n return is_undefined(val)\n ? error(label_name, \"undefined label -- assemble\")\n : tail(val);\n}\n```", - "token_count": 90, - "has_code": true, - "chapter": "Computing with Register Machines", - "section": "A Register-Machine Simulator", - "subsection": "The Assembler", - "chunk_index": 3, - "chunk_id": "Computing_with_Register_Machines_The_Assembler_3" - }, - { - "content": "The machine model generated by\nmake_machine\nis represented as a\nfunction\nwith local state using the message-passing techniques\ndeveloped in chapter.\n\nTo build this model,\nmake_machine\nbegins by calling the\nfunction\nmake_new_machine\nto construct\nthe parts of the machine model that are common to all register\nmachines.\n\nThis basic machine model constructed by\nmake_new_machine\nis essentially a container for some registers and a stack, together with an\nexecution mechanism that processes the controller instructions one by one.\n\n```javascript\nThe function\n\tmake_machine\n```\n\nthen extends this basic model (by sending it\nmessages) to include the registers, operations, and controller of the\nparticular machine being defined.\n\nFirst it allocates a register in\nthe new machine for each of the supplied register names and installs\nthe designated operations in the machine.\n\nThen it uses an\nassembler (described below in\nsection ) to transform the controller list\ninto instructions for the new machine and installs these as the\nmachine s instruction sequence.\n\n```javascript\nThe function\n\tmake_machine\n```\n\nreturns as its value the modified machine model.\n\n```javascript\ngcd_machine_complete_example\n make_machine\n start\n\nconst gcd_machine =\n make_machine(\n list(\"a\", \"b\", \"t\"),\n list(list(\"rem\", (a, b) => a % b),\n list(\"=\", (a, b) => a === b)),\n list(\n \"test_b\",\n test(list(op(\"=\"), reg(\"b\"), constant(0))),\n branch(label(\"gcd_done\")),\n assign(\"t\", list(op(\"rem\"), reg(\"a\"), reg(\"b\"))),\n assign(\"a\", reg(\"b\")),\n assign(\"b\", reg(\"t\")),\n go_to(label(\"test_b\")),\n \"gcd_done\"));\nset_register_contents(gcd_machine, \"a\", 206);\nset_register_contents(gcd_machine, \"b\", 40);\nstart(gcd_machine);\nget_register_contents(gcd_machine, \"a\");\n```\n\n```javascript\nmake_machine\n assemble\n make_new_machine\n gcd_machine_complete_example\n\nfunction make_machine(register_names, ops, controller) {\n const machine = make_new_machine();\n for_each(register_name =>\n machine(\"allocate_register\")(register_name),\n register_names);\n machine(\"install_operations\")(ops);\n machine(\"install_instruction_sequence\")\n (assemble(controller, machine));\n return machine;\n}\n```\n\nWe will represent a register as a\nfunction\nwith local state, as in\nchapter.\n\nThe\nfunction\nmake_register\ncreates a register that\nholds a value that can be accessed or changed:", - "token_count": 278, + "content": "The machine model generated by\nmake_machine\nis represented as a\nfunction\nwith local state using the message-passing techniques\ndeveloped in chapter.\n\nTo build this model,\nmake_machine\nbegins by calling the\nfunction\nmake_new_machine\nto construct\nthe parts of the machine model that are common to all register\nmachines.\n\nThis basic machine model constructed by\nmake_new_machine\nis essentially a container for some registers and a stack, together with an\nexecution mechanism that processes the controller instructions one by one.\n\nThe function make_machine\nthen extends this basic model (by sending it\nmessages) to include the registers, operations, and controller of the\nparticular machine being defined.\n\nFirst it allocates a register in\nthe new machine for each of the supplied register names and installs\nthe designated operations in the machine.\n\nThen it uses an\nassembler (described below in\nsection ) to transform the controller list\ninto instructions for the new machine and installs these as the\nmachine s instruction sequence.\n\nThe function make_machine\nreturns as its value the modified machine model.\n\n```javascript\ngcd_machine_complete_example\n make_machine\n start\n\nconst gcd_machine =\n make_machine(\n list(\"a\", \"b\", \"t\"),\n list(list(\"rem\", (a, b) => a % b),\n list(\"=\", (a, b) => a === b)),\n list(\n \"test_b\",\n test(list(op(\"=\"), reg(\"b\"), constant(0))),\n branch(label(\"gcd_done\")),\n assign(\"t\", list(op(\"rem\"), reg(\"a\"), reg(\"b\"))),\n assign(\"a\", reg(\"b\")),\n assign(\"b\", reg(\"t\")),\n go_to(label(\"test_b\")),\n \"gcd_done\"));\nset_register_contents(gcd_machine, \"a\", 206);\nset_register_contents(gcd_machine, \"b\", 40);\nstart(gcd_machine);\nget_register_contents(gcd_machine, \"a\");\n```\n\n```javascript\nmake_machine\n assemble\n make_new_machine\n gcd_machine_complete_example\n\nfunction make_machine(register_names, ops, controller) {\n const machine = make_new_machine();\n for_each(register_name =>\n machine(\"allocate_register\")(register_name),\n register_names);\n machine(\"install_operations\")(ops);\n machine(\"install_instruction_sequence\")\n (assemble(controller, machine));\n return machine;\n}\n```\n\nWe will represent a register as a\nfunction\nwith local state, as in\nchapter.\n\nThe\nfunction\nmake_register\ncreates a register that\nholds a value that can be accessed or changed:", + "token_count": 274, "has_code": true, "chapter": "Computing with Register Machines", "section": "A Register-Machine Simulator", @@ -1960,8 +2000,8 @@ "chunk_id": "Computing_with_Register_Machines_The_Machine_Model_1" }, { - "content": "The\nfunction\nmake_register\ncreates a register that\nholds a value that can be accessed or changed:\n\n```javascript\nmake_register\n gcd_machine_complete_example\n\nfunction make_register(name) {\n let contents = \"*unassigned*\";\n function dispatch(message) {\n return message === \"get\"\n ? contents\n : message === \"set\"\n ? value => { contents = value; }\n : error(message, \"unknown request -- make_register\");\n }\n return dispatch;\n}\n```\n\nThe following functions are used to access registers:\n\n```javascript\nget_contents\n gcd_machine_complete_example\n\nfunction get_contents(register) {\n return register(\"get\");\n}\nfunction set_contents(register, value) {\n return register(\"set\")(value);\n}\n```\n\nWe can also represent a stack as a\nfunction\nwith local state.\n\nThe\nfunction\nmake_@stack\ncreates a stack whose local state consists\nof a list of the items on the stack.\n\nA stack accepts requests to\n\n```javascript\nmake_stack\n gcd_machine_complete_example\n\nfunction make_stack() {\n let stack = null;\n let frame = null;\n function push_marker() {\n frame = pair(stack, frame);\n return \"done\";\n }\n function pop_marker() {\n stack = head(frame);\n frame = tail(frame);\n return \"done\";\n }\n function push(x) {\n stack = pair(x, stack);\n return \"done\";\n }\n function pop() {\n if (is_null(stack)) {\n error(\"empty stack -- pop\");\n } else {\n const top = head(stack);\n stack = tail(stack);\n return top;\n }\n }\n function initialize() {\n stack = null;\n return \"done\";\n }\n function dispatch(message) {\n return message === \"push\"\n ? push\n : message === \"pop\"\n ? pop()\n : message === \"push_marker\"\n ? push_marker()\n : message === \"pop_marker\"\n ? pop_marker()\n : message === \"initialize\"\n ? initialize()\n : error(message, \"unknown request -- stack\");\n }\n return dispatch;\n}\n\nfunction make_push_marker_to_stack_ef(machine, stack, pc) {\n return () => {\n push_marker(stack);\n advance_pc(pc);\n };\n}\nfunction make_revert_stack_to_marker_ef(machine, stack, pc) {\n return () => {\n pop_marker(stack);\n advance_pc(pc);\n };\n}\n\nfunction push_marker_to_stack() { return list(\"push_marker_to_stack\"); }\nfunction revert_stack_to_marker() { return list(\"revert_stack_to_marker\"); }\n\nfunction pop_marker(stack) {\n return stack(\"pop_marker\");\n}\nfunction push_marker(stack) {\n return stack(\"push_marker\");\n}\n```", - "token_count": 292, + "content": "The\nfunction\nmake_register\ncreates a register that\nholds a value that can be accessed or changed:\n\nThe following functions are used to access registers:\n\n```javascript\nget_contents\n gcd_machine_complete_example\n\nfunction get_contents(register) {\n return register(\"get\");\n}\nfunction set_contents(register, value) {\n return register(\"set\")(value);\n}\n```\n\nWe can also represent a stack as a\nfunction\nwith local state.\n\nThe\nfunction\nmake_@stack\ncreates a stack whose local state consists\nof a list of the items on the stack.\n\nA stack accepts requests to\nan item onto the stack, to\nthe top item off the stack\nand return it, and to\nthe stack to empty.\n\n```javascript\nmake_stack\n gcd_machine_complete_example\n\nfunction make_stack() {\n let stack = null;\n let frame = null;\n function push_marker() {\n frame = pair(stack, frame);\n return \"done\";\n }\n function pop_marker() {\n stack = head(frame);\n frame = tail(frame);\n return \"done\";\n }\n function push(x) {\n stack = pair(x, stack);\n return \"done\";\n }\n function pop() {\n if (is_null(stack)) {\n error(\"empty stack -- pop\");\n } else {\n const top = head(stack);\n stack = tail(stack);\n return top;\n }\n }\n function initialize() {\n stack = null;\n return \"done\";\n }\n function dispatch(message) {\n return message === \"push\"\n ? push\n : message === \"pop\"\n ? pop()\n : message === \"push_marker\"\n ? push_marker()\n : message === \"pop_marker\"\n ? pop_marker()\n : message === \"initialize\"\n ? initialize()\n : error(message, \"unknown request -- stack\");\n }\n return dispatch;\n}\n\nfunction make_push_marker_to_stack_ef(machine, stack, pc) {\n return () => {\n push_marker(stack);\n advance_pc(pc);\n };\n}\nfunction make_revert_stack_to_marker_ef(machine, stack, pc) {\n return () => {\n pop_marker(stack);\n advance_pc(pc);\n };\n}\n\nfunction push_marker_to_stack() { return list(\"push_marker_to_stack\"); }\nfunction revert_stack_to_marker() { return list(\"revert_stack_to_marker\"); }\n\nfunction pop_marker(stack) {\n return stack(\"pop_marker\");\n}\nfunction push_marker(stack) {\n return stack(\"push_marker\");\n}\n```", + "token_count": 271, "has_code": true, "chapter": "Computing with Register Machines", "section": "A Register-Machine Simulator", @@ -1970,7 +2010,7 @@ "chunk_id": "Computing_with_Register_Machines_The_Machine_Model_2" }, { - "content": "A stack accepts requests to\n\n```javascript\nfunction make_stack() {\n let stack = null;\n function push(x) {\n stack = pair(x, stack);\n return \"done\";\n }\n function pop() {\n if (is_null(stack)) {\n error(\"empty stack -- pop\");\n } else {\n const top = head(stack);\n stack = tail(stack);\n return top;\n }\n }\n function initialize() {\n stack = null;\n return \"done\";\n }\n function dispatch(message) {\n return message === \"push\"\n ? push\n : message === \"pop\"\n ? pop()\n : message === \"initialize\"\n ? initialize()\n : error(message, \"unknown request -- stack\");\n }\n return dispatch;\n}\n```\n\nThe following functions are used to access stacks:\n\n```javascript\npop\n gcd_machine_complete_example\n\nfunction pop(stack) {\n return stack(\"pop\");\n}\nfunction push(stack, value) {\n return stack(\"push\")(value);\n}\n```\n\nThe\nmake_new_machine\nfunction,\nshown in figure , constructs an\nobject whose local state consists of a stack, an initially empty instruction\nsequence, a list of operations that initially contains an operation to\nregister table that initially contains two\nregisters, named\nprogram counter ).\n\nThe internal\nfunction\nallocate_register\nadds new entries to the register table, and the internal\nfunction\nlookup_register\nlooks up registers in the table.\n\nThe Our test instructions set the contents of Our branch instructions decide whether or not to branch by examining the contents of\n\nThe\nfunction\nfunction\nof no arguments, called the\ninstruction execution function,\nsuch that calling this\nfunction\nsimulates executing the instruction.\n\nAs the simulation runs,\nThe function execute\ngets that instruction, executes it by calling the instruction execution\nfunction,\nand repeats this cycle until there are no more instructions to execute\n(i.e., until\n\nAs part of its operation, each instruction execution function modifies\n\n```javascript\nThe instructions\n\tbranch\n\tand\n\tgo_to\n```\n\nchange function changes the contents of\n\n```javascript\nThe function\n\tmake_new_machine\n```\n\nreturns a\ndispatch\nfunction\nthat implements message-passing access to the internal state.\n\nNotice that\nstarting the machine is accomplished by setting", + "content": "A stack accepts requests to\nan item onto the stack, to\nthe top item off the stack\nand return it, and to\nthe stack to empty.\n\nThe following functions are used to access stacks:\n\n```javascript\npop\n gcd_machine_complete_example\n\nfunction pop(stack) {\n return stack(\"pop\");\n}\nfunction push(stack, value) {\n return stack(\"push\")(value);\n}\n```\n\nThe\nmake_new_machine\nfunction,\nshown in figure , constructs an\nobject whose local state consists of a stack, an initially empty instruction\nsequence, a list of operations that initially contains an operation to\ninitialize the stack, and a\nregister table that initially contains two\nregisters, named\nand\n(for program counter ).\n\nThe internal\nfunction\nallocate_register\nadds new entries to the register table, and the internal\nfunction\nlookup_register\nlooks up registers in the table.\n\nThe register is used to control branching\nin the simulated machine.\n\nOur test\ninstructions set the contents of\nto the result of the test (true or false).\n\nOur branch\ninstructions decide whether or not to branch by examining the contents of.\n\nThe register determines the sequencing of\ninstructions as the machine runs.\n\nThis sequencing is implemented by the\ninternal\nfunction.\n\nIn the simulation model, each machine instruction is a data structure\nthat includes a\nfunction\nof no arguments, called the\ninstruction execution function,\nsuch that calling this\nfunction\nsimulates executing the instruction.\n\nAs the simulation runs,\npoints to the place in the instruction\nsequence beginning with the next instruction to be executed.\n\nThe function execute\ngets that instruction, executes it by calling the instruction execution\nfunction,\nand repeats this cycle until there are no more instructions to execute\n(i.e., until points to the end of the\ninstruction sequence).\n\nAs part of its operation, each instruction execution\nfunction\nmodifies\nto indicate the next instruction to be\nexecuted.\n\nThe instructions branch and go_to\nchange to point to the new destination.", "token_count": 301, "has_code": true, "chapter": "Computing with Register Machines", @@ -1980,8 +2020,8 @@ "chunk_id": "Computing_with_Register_Machines_The_Machine_Model_3" }, { - "content": "Notice that\nstarting the machine is accomplished by setting\n\nFor convenience, we provide an alternate interface to a machine s functions to set and examine register contents, as specified at the beginning of section\n\n:\n\n```javascript\nstart\n gcd_machine_complete_example\n\nfunction start(machine) {\n return machine(\"start\");\n}\nfunction get_register_contents(machine, register_name) {\n return get_contents(get_register(machine, register_name));\n}\nfunction set_register_contents(machine, register_name, value) {\n set_contents(get_register(machine, register_name), value);\n return \"done\";\n}\n```\n\nThese functions (and many functions in sections and ) use the following to look up the register with a given name in a given machine:\n\n```javascript\nget_register\n gcd_machine_complete_example\n\nfunction get_register(machine, reg_name) {\n return machine(\"get_register\")(reg_name);\n}\n```", - "token_count": 100, + "content": "The instructions branch and go_to\nchange to point to the new destination.\n\nObserve that\neach call to calls\nagain, but this does not produce an\ninfinite loop because running the instruction execution\nfunction\nchanges the contents of.\n\nThe function make_new_machine\nreturns a\ndispatch\nfunction\nthat implements message-passing access to the internal state.\n\nNotice that\nstarting the machine is accomplished by setting\nto the beginning of the instruction sequence\nand calling.\n\nFor convenience, we provide an alternate interface to a machine s operation, as well as functions to set and examine register contents, as specified at\n\nthe beginning of section :\n\n```javascript\nstart\n gcd_machine_complete_example\n\nfunction start(machine) {\n return machine(\"start\");\n}\nfunction get_register_contents(machine, register_name) {\n return get_contents(get_register(machine, register_name));\n}\nfunction set_register_contents(machine, register_name, value) {\n set_contents(get_register(machine, register_name), value);\n return \"done\";\n}\n```\n\nThese functions (and many functions in sections and ) use the following to look up the register with a given name in a given machine:\n\n```javascript\nget_register\n gcd_machine_complete_example\n\nfunction get_register(machine, reg_name) {\n return machine(\"get_register\")(reg_name);\n}\n```", + "token_count": 165, "has_code": true, "chapter": "Computing with Register Machines", "section": "A Register-Machine Simulator", @@ -1990,8 +2030,8 @@ "chunk_id": "Computing_with_Register_Machines_The_Machine_Model_4" }, { - "content": "The explicit-control evaluator of\nsection is a\nregister machine whose controller interprets\nJavaScript\nprograms.\n\nIn this\nsection we will see how to run\nJavaScript\nprograms on a register machine whose controller is not a\nJavaScript\ninterpreter.\n\nThe explicit-control evaluator machine is\nit\ncan carry out any computational process that can be described in\nJavaScript.\n\nThe\nevaluator s controller orchestrates the use of its data\npaths to perform the desired computation.\n\nThus, the\nevaluator s data paths are universal: They are sufficient\nto perform any computation we desire, given an appropriate\ncontroller.\n\nCommercial\nnative language of the machine, or simply\nmachine language.\n\nPrograms written in machine language are\nsequences of instructions that use the machine s data paths.\n\nFor example, the\ns instruction sequence\ncan be thought of as a machine-language program for a general-purpose\ncomputer rather than as the controller for a specialized interpreter\nmachine.\n\nThere are two common strategies for bridging the gap between\nhigher-level languages and register-machine languages.\n\nThe explicit-control evaluator illustrates the\nstrategy of interpretation.\n\nAn interpreter written in the native\nlanguage of a machine configures the machine to execute programs\nwritten in a language (called the\nsource language ) that may\ndiffer from the native language of the machine performing the\nevaluation.\n\nThe primitive\nfunctions\nof the source language are implemented as a library of subroutines written\nin the native language of the given machine.\n\nA program to be interpreted\n(called the\nsource program ) is represented as a data structure.\n\nThe interpreter\ntraverses this data structure, analyzing the source program.\n\nAs it\ndoes so, it simulates the intended behavior of the source program by\ncalling appropriate primitive subroutines from the library.\n\nIn this section, we explore the alternative strategy of\ncompilation.", - "token_count": 286, + "content": "The explicit-control evaluator of\nsection is a\nregister machine whose controller interprets\nJavaScript\nprograms.\n\nIn this\nsection we will see how to run\nJavaScript\nprograms on a register machine whose controller is not a\nJavaScript\ninterpreter.\n\nThe explicit-control evaluator machine is\nuniversal it\ncan carry out any computational process that can be described in\nJavaScript.\n\nThe\nevaluator s controller orchestrates the use of its data\npaths to perform the desired computation.\n\nThus, the\nevaluator s data paths are universal: They are sufficient\nto perform any computation we desire, given an appropriate\ncontroller.\n\nCommercial\ngeneral-purpose computers are\nregister machines organized\naround a collection of registers and operations that constitute\nan efficient and convenient universal set of data paths.\n\nThe controller for a general-purpose machine is an interpreter for\na register-machine language like the one we have been using.\n\nThis\nlanguage is called the\nnative language of the machine, or simply\nmachine language.\n\nPrograms written in machine language are\nsequences of instructions that use the machine s data paths.\n\nFor example, the\nexplicit-control evaluator s instruction sequence\ncan be thought of as a machine-language program for a general-purpose\ncomputer rather than as the controller for a specialized interpreter\nmachine.\n\nThere are two common strategies for bridging the gap between\nhigher-level languages and register-machine languages.\n\nThe explicit-control evaluator illustrates the\nstrategy of interpretation.\n\nAn interpreter written in the native\nlanguage of a machine configures the machine to execute programs\nwritten in a language (called the\nsource language ) that may\ndiffer from the native language of the machine performing the\nevaluation.\n\nThe primitive\nfunctions\nof the source language are implemented as a library of subroutines written\nin the native language of the given machine.\n\nA program to be interpreted\n(called the\nsource program ) is represented as a data structure.", + "token_count": 297, "has_code": false, "chapter": "Computing with Register Machines", "section": "Compilation", @@ -2000,8 +2040,8 @@ "chunk_id": "Computing_with_Register_Machines_Compilation_1" }, { - "content": "In this section, we explore the alternative strategy of\ncompilation.\n\nA compiler for a given source language and machine\ntranslates a source program into an equivalent program (called the\nobject program ) written in the machine s native language.\n\nThe compiler that we implement in this section translates programs written in\nJavaScript\ninto sequences of instructions to be executed using the explicit-control\nevaluator machine s data paths.\n\nCompared with interpretation, compilation can provide a great increase\nin the efficiency of program execution, as we will explain below in\nthe overview of the compiler.\n\nOn the other hand, an interpreter\nprovides a more powerful environment for interactive program\ndevelopment and debugging, because the source program being executed\nis available at run time to be examined and modified.\n\nIn addition,\nbecause the entire library of primitives is present, new programs can\nbe constructed and added to the system during debugging.\n\nIn view of the complementary advantages of compilation and\ninterpretation, modern\nprogram-development environments\npursue a mixed\nstrategy.\n\nThese systems\nare generally organized so that interpreted\nfunctions\nand compiled\nfunctions\ncan call each other.\n\nThis enables a programmer to compile those parts of a\nprogram that are assumed to be debugged, thus gaining the efficiency\nadvantage of compilation, while retaining the interpretive mode of execution\nfor those parts of the program that are in the flux of interactive\ndevelopment and\ndebugging.\n\nIn section , after\nwe have implemented the compiler, we will show how to interface it\nwith our interpreter to produce an integrated\ninterpreter-compiler\nsystem.\n\nOur compiler is much like our interpreter, both in its structure and in\nthe function it performs.\n\nAccordingly, the mechanisms used by the\ncompiler for analyzing\ncomponents\nwill be similar to those used by\nthe interpreter.", - "token_count": 289, + "content": "A program to be interpreted\n(called the\nsource program ) is represented as a data structure.\n\nAs it\ndoes so, it simulates the intended behavior of the source program by\ncalling appropriate primitive subroutines from the library.\n\nIn this section, we explore the alternative strategy of\ncompilation.\n\nA compiler for a given source language and machine\ntranslates a source program into an equivalent program (called the\nobject program ) written in the machine s native language.\n\nThe compiler that we implement in this section translates programs written in\nJavaScript\ninto sequences of instructions to be executed using the explicit-control\nevaluator machine s data paths.\n\nCompared with interpretation, compilation can provide a great increase\nin the efficiency of program execution, as we will explain below in\nthe overview of the compiler.\n\nOn the other hand, an interpreter\nprovides a more powerful environment for interactive program\ndevelopment and debugging, because the source program being executed\nis available at run time to be examined and modified.\n\nIn addition,\nbecause the entire library of primitives is present, new programs can\nbe constructed and added to the system during debugging.\n\nIn view of the complementary advantages of compilation and\ninterpretation, modern\nprogram-development environments\npursue a mixed\nstrategy.\n\nThese systems\nare generally organized so that interpreted\nfunctions\nand compiled\nfunctions\ncan call each other.\n\nThis enables a programmer to compile those parts of a\nprogram that are assumed to be debugged, thus gaining the efficiency\nadvantage of compilation, while retaining the interpretive mode of execution\nfor those parts of the program that are in the flux of interactive\ndevelopment and\ndebugging.\n\nIn section , after\nwe have implemented the compiler, we will show how to interface it\nwith our interpreter to produce an integrated\ninterpreter-compiler\nsystem.", + "token_count": 290, "has_code": false, "chapter": "Computing with Register Machines", "section": "Compilation", @@ -2010,9 +2050,9 @@ "chunk_id": "Computing_with_Register_Machines_Compilation_2" }, { - "content": "Accordingly, the mechanisms used by the\ncompiler for analyzing\ncomponents\nwill be similar to those used by\nthe interpreter.\n\nMoreover, to make it easy to interface compiled and\ninterpreted code, we will design the compiler to generate code that\nobeys the same conventions of\nfunction\nto be applied will be in\nfun,\nfunctions\nwill return their answers in\nfunction\nshould return will be kept in\n\nThis description suggests a strategy for implementing a rudimentary\ncompiler: We traverse the\ncomponent\nin the same way the\ninterpreter does.\n\nWhen we encounter a register instruction that the\ninterpreter would perform in evaluating the\ncomponent,\nwe do not\nexecute the instruction but instead accumulate it into a sequence.\n\nThe\nresulting sequence of instructions will be the object code.\n\nObserve\nthe\na componentfor example,\nf(96, 22)it\nperforms the work of classifying the\ncomponent\n(discovering that this is a\nfunction\napplication) and\ntesting for the end of the\n\n```javascript\nlist of argument expressions\n\t(discovering that there are two argument expressions).\n```\n\nWith a\ncompiler, the\ncomponent\nis analyzed only once, when the\ninstruction sequence is generated at compile time.\n\nThe object code\nproduced by the compiler contains only the instructions that evaluate\nthe\nfunction expression and the two argument expressions,\nassemble the argument list, and apply the\nfunction (in\nto the arguments (in\n\nThis is the same kind of optimization we implemented in the.\n\nBut there are further opportunities to gain efficiency in compiled code.\n\nAs the interpreter runs, it follows a process that must be applicable\nto any\ncomponent\nin the language.\n\nIn contrast, a given segment of\ncompiled code is meant to execute some particular\ncomponent.\n\nThis can make a big difference, for example in the use of the\nstack to save registers.\n\nWhen the interpreter evaluates\na component,\nit must be prepared for any contingency.", - "token_count": 302, - "has_code": true, + "content": "In section , after\nwe have implemented the compiler, we will show how to interface it\nwith our interpreter to produce an integrated\ninterpreter-compiler\nsystem.\n\nAccordingly, the mechanisms used by the\ncompiler for analyzing\ncomponents\nwill be similar to those used by\nthe interpreter.\n\nMoreover, to make it easy to interface compiled and\ninterpreted code, we will design the compiler to generate code that\nobeys the same conventions of\nregister usage as the interpreter: The\nenvironment will be kept in the register,\nargument lists will be accumulated in , a\nfunction\nto be applied will be in\nfun,\nfunctions\nwill return their answers in ,\nand the location to which a\nfunction\nshould return will be kept in.\n\nIn general, the compiler translates a source program into an object\nprogram that performs essentially the same register operations as\nwould the interpreter in evaluating the same source program.\n\nThis description suggests a strategy for implementing a rudimentary\ncompiler: We traverse the\ncomponent\nin the same way the\ninterpreter does.\n\nWhen we encounter a register instruction that the\ninterpreter would perform in evaluating the\ncomponent,\nwe do not\nexecute the instruction but instead accumulate it into a sequence.\n\nThe\nresulting sequence of instructions will be the object code.\n\nObserve\nthe\nefficiency advantage of compilation over interpretation.\n\nEach\ntime the interpreter evaluates\na componentfor example,\nf(96, 22)it\nperforms the work of classifying the\ncomponent\n(discovering that this is a\nfunction\napplication) and\ntesting for the end of the\nlist of argument expressions (discovering that there are two argument expressions).\n\nWith a\ncompiler, the\ncomponent\nis analyzed only once, when the\ninstruction sequence is generated at compile time.", + "token_count": 274, + "has_code": false, "chapter": "Computing with Register Machines", "section": "Compilation", "subsection": null, @@ -2020,8 +2060,8 @@ "chunk_id": "Computing_with_Register_Machines_Compilation_3" }, { - "content": "When the interpreter evaluates\na component,\nit must be prepared for any contingency.\n\nBefore evaluating a\nsubcomponent,\nthe interpreter saves all registers that will be needed later, because the\nsubcomponent\nmight require an arbitrary evaluation.\n\nA compiler, on the other hand, can exploit the structure of the particular\ncomponent\nit is processing to generate code that avoids\nunnecessary stack operations.\n\nAs a case in point, consider the\napplication f(96, 22).\n\nBefore the interpreter evaluates the\nfunction expression of the application,\nit prepares\nfor this evaluation by saving the registers containing the\nargument expressions\nand the environment, whose values will be needed later.\n\nThe interpreter then\nevaluates the\nfunction expression\nto obtain the result in\nHowever, in the particular expression we\nare dealing with, the\nfunction expression\nis the\nname\nlookup_symbol_value,\nwhich does not alter any registers.\n\nThe compiler that we implement in\nthis section will take advantage of this fact and generate code that\nevaluates the\nfunction expression\nusing the instruction\n\n```javascript\nassign(\"fun\",\n list(op(\"lookup_symbol_value\"), constant(\"f\"), reg(\"env\")))\n```\n\n```javascript\nwhere the argument to lookup_symbol_value\n\tis extracted at compile time from the parser's representation of f(96, 22).\n```\n\nThis code not only avoids the unnecessary saves and restores but also assigns the value of the lookup directly to whereas the interpreter would obtain\n\nthe result in\n\nA compiler can also optimize access to the environment.\n\nHaving\nanalyzed the code, the compiler can\nknow in which frame\nthe value of a particular name\nwill be located and access that frame directly,\nrather than performing the\nlookup_@symbol_@value\nsearch.\n\nWe will discuss how to implement such\nlexical addressing\nin\nsection.\n\nUntil then, however,\nwe will focus on the kind of register and stack optimizations described\nabove.", - "token_count": 281, + "content": "With a\ncompiler, the\ncomponent\nis analyzed only once, when the\ninstruction sequence is generated at compile time.\n\nThis is the same kind of optimization we implemented in the\nanalyzing evaluator of section.\n\nBut there are further opportunities to gain efficiency in compiled code.\n\nAs the interpreter runs, it follows a process that must be applicable\nto any\ncomponent\nin the language.\n\nIn contrast, a given segment of\ncompiled code is meant to execute some particular\ncomponent.\n\nThis can make a big difference, for example in the use of the\nstack to save registers.\n\nWhen the interpreter evaluates\na component,\nit must be prepared for any contingency.\n\nBefore evaluating a\nsubcomponent,\nthe interpreter saves all registers that will be needed later, because the\nsubcomponent\nmight require an arbitrary evaluation.\n\nA compiler, on the other hand, can exploit the structure of the particular\ncomponent\nit is processing to generate code that avoids\nunnecessary stack operations.\n\nAs a case in point, consider the\napplication f(96, 22).\n\nBefore the interpreter evaluates the\nfunction expression of the application,\nit prepares\nfor this evaluation by saving the registers containing the\nargument expressions\nand the environment, whose values will be needed later.\n\nThe interpreter then\nevaluates the\nfunction expression\nto obtain the result in\n, restores the saved registers, and finally\nmoves the result from to.\n\nHowever, in the particular expression we\nare dealing with, the\nfunction expression\nis the\nname\n, whose evaluation is\naccomplished by the machine operation\nlookup_symbol_value,\nwhich does not alter any registers.\n\nThe compiler that we implement in\nthis section will take advantage of this fact and generate code that\nevaluates the\nfunction expression\nusing the instruction\n\n```javascript\nassign(\"fun\",\n list(op(\"lookup_symbol_value\"), constant(\"f\"), reg(\"env\")))\n```\n\nwhere the argument to lookup_symbol_value is extracted at compile time from the parser's representation of f(96, 22).", + "token_count": 299, "has_code": true, "chapter": "Computing with Register Machines", "section": "Compilation", @@ -2030,8 +2070,8 @@ "chunk_id": "Computing_with_Register_Machines_Compilation_4" }, { - "content": "Until then, however,\nwe will focus on the kind of register and stack optimizations described\nabove.\n\nThere are many other optimizations that can be performed by a\ncompiler, such as coding primitive operations in line instead\nof using a general ); but we will not emphasize these\nhere.\n\nOur main goal in this section is to illustrate the compilation process\nin a simplified (but still interesting) context.", - "token_count": 67, + "content": "where the argument to lookup_symbol_value is extracted at compile time from the parser's representation of f(96, 22).\n\nA compiler can also optimize access to the environment.\n\nHaving\nanalyzed the code, the compiler can\nknow in which frame\nthe value of a particular name\nwill be located and access that frame directly,\nrather than performing the\nlookup_@symbol_@value\nsearch.\n\nWe will discuss how to implement such\nlexical addressing\nin\nsection.\n\nUntil then, however,\nwe will focus on the kind of register and stack optimizations described\nabove.\n\nThere are many other optimizations that can be performed by a\ncompiler, such as coding primitive operations in line instead\nof using a general mechanism (see\nexercise ); but we will not emphasize these\nhere.\n\nOur main goal in this section is to illustrate the compilation process\nin a simplified (but still interesting) context.", + "token_count": 138, "has_code": false, "chapter": "Computing with Register Machines", "section": "Compilation", @@ -2040,338 +2080,1538 @@ "chunk_id": "Computing_with_Register_Machines_Compilation_5" }, { - "content": "To design a register machine, we must design its data paths\n(registers and operations) and the controller that sequences\nthese operations.\n\nTo illustrate the design of a simple register\nmachine, let us examine Euclid s Algorithm, which is used to compute\n,\ns Algorithm can be\ncarried out by an iterative process, as specified by the following\nfunction:\n\n```javascript\ngcd_example\n\nfunction gcd(a, b) {\n return b === 0 ? a : gcd(b, a % b);\n}\n```\n\nA machine to carry out this algorithm must keep track of two numbers,\n$a$ and $b$ , so let us\nassume that these numbers are stored in two registers with those names.\n\nThe\nbasic operations required are testing whether the contents of register\ntemporary register, which we call\n\nWe can illustrate the registers and operations required for this\nmachine by using the.\n\nIn this\ndiagram, the registers ( a buttondrawn as $\\otimes$ behind the\nhead, pointing from the source of data to the register.\n\nWhen pushed, the button allows\nthe value at the source to flow into the designated register.\n\nThe label next to each button is the name we will use to refer to the\nbutton.\n\nThe names are arbitrary, and can be chosen to have mnemonic value\n(for example,\n\nAn operation that computes a value from constants and the contents\nof registers is represented in a data-path diagram by a trapezoid\ncontaining a name for the operation.\n\nFor example, the box marked\nrepresents an operation that\ncomputes the remainder of the contents of the registers\ns output value\nto registers.\n\nA test is represented by a circle containing a name for the\ntest.", - "token_count": 272, + "content": "Now that we have seen all the elements of the compiler, let us examine\nan example of compiled code to see how things fit together.\n\nWe will\ncompile the\ndeclaration\nof a recursive\nfactorial\nfunction\nby passing as first argument to the result of applying to a string representation of the program (here using back quotes `$\\ldots$`, which work like single and double quotation marks but allow the string to span multiple lines):\n\n```javascript\ncompile(parse(`\nfunction factorial(n) {\n return n === 1\n ? 1\n : factorial(n - 1) * n;\n}\n `),\n \"val\",\n \"next\");\n```\n\nWe have specified that the value of the\ndeclaration\nshould be placed in the register.\n\nWe don t care what the compiled\ncode does after executing the\ndeclaration,\nso our choice of\n\"next\"\nas the linkage\ndescriptor is arbitrary.\n\nThe function determines that it was given a function declaration, so it transforms it to a constant declaration and then\ncalls\ncompile_declaration.\n\nThis compiles\ncode to compute the value to be assigned (targeted to\n), followed by code to install the\ndeclaration,\nfollowed by code to put the value of the\ndeclaration (which is the value )\ninto the target register, followed finally by the linkage code.\n\nThe register\nis preserved around the computation of the\nvalue, because it is needed in order to install the\ndeclaration.\n\nBecause\nthe linkage is\n\"next\",\nthere is no linkage code\nin this case.\n\nThe skeleton of the compiled code is thus\n\n```javascript\nsave env if modified by code to compute value\ncompilation of declaration value, target val, linkage \"next\"\nrestore env if saved above\nperform(list(op(\"assign_symbol_value\"),\n constant(\"factorial\"),\n reg(\"val\"),\n reg(\"env\"))),\nassign(\"val\", constant(undefined))\n```\n\nThe expression that is\ncompiled to produce the value for the\nname\nis a\nlambda\nexpression whose value is the\nfunction\nthat computes factorials.", + "token_count": 296, "has_code": true, "chapter": "Computing with Register Machines", - "section": "Designing Register Machines", - "subsection": null, + "section": "Compilation", + "subsection": "An Example of Compiled Code", "chunk_index": 1, - "chunk_id": "Computing_with_Register_Machines_Designing_Register_Machines_1" + "chunk_id": "Computing_with_Register_Machines_An_Example_of_Compiled_Code_1" }, { - "content": "A test is represented by a circle containing a name for the\ntest.\n\nFor example, our GCD machine has an operation that tests whether the\ncontents of register $\\otimes$ buttons as switches, the data-path diagram\nis very like the wiring diagram for a machine that could be constructed\nfrom electrical components.\n\nData paths for a GCD machine.\n\nIn order for the data paths to actually compute GCDs, the buttons must\nbe pushed in the correct sequence.\n\nWe will describe this sequence in\nterms of a.\n\nThe elements of the\ncontroller diagram indicate how the data-path components should be operated.\n\nThe rectangular boxes in the controller diagram identify data-path buttons\nto be pushed, and the arrows describe the sequencing from one step to the\nnext.\n\nThe diamond in the diagram represents a decision.\n\nOne of the two\nsequencing arrows will be followed, depending on the value of the data-path\ntest identified in the diamond.\n\nWe can interpret the controller in terms\nof a physical analogy: Think of the diagram as a maze in which a marble is\nrolling.\n\nWhen the marble rolls into a box, it pushes the data-path button\nthat is named by the box.\n\nWhen the marble rolls into a decision node (such\nas the test for\n$\\, =0$ ), it leaves\nthe node on the path determined by the result of the indicated test.\n\nTaken together, the data paths and the controller completely describe\na machine for computing GCDs.\n\nWe start the controller (the rolling\nmarble) at the place marked", - "token_count": 252, - "has_code": false, - "chapter": "Computing with Register Machines", - "section": "Designing Register Machines", - "subsection": null, + "content": "The expression that is\ncompiled to produce the value for the\nname\nis a\nlambda\nexpression whose value is the\nfunction\nthat computes factorials.\n\nThe sequence then skips around\nthe compiled\nfunction\ncode, which is inserted at this point.\n\nThe\nfunction\ncode itself begins by extending the\nfunctions declaration\nenvironment by a frame that binds the\nparameter to the\nfunction\nargument.\n\nThen comes the actual\nfunction\nbody.\n\nSince this code for the value of the\nname\ndoesn t modify the register, the\noptional\nand shown above aren t\ngenerated.\n\n(The\nfunction\ncode at\nentry1\nisn t executed at this point,\nso its use of is irrelevant.)\nTherefore, the skeleton for the compiled code becomes\n\n```javascript\n$\\texttt{ }\\texttt{ }$assign(\"val\", list(op(\"make_compiled_function\"),\n label(\"entry1\"),\n reg(\"env\"))),\n go_to(label(\"after_lambda2\")),\n\"entry1\",\n assign(\"env\", list(op(\"compiled_function_env\"), reg(\"fun\"))),\n assign(\"env\", list(op(\"extend_environment\"),\n constant(list(\"n\")),\n reg(\"argl\"),\n reg(\"env\"))),\n compilation of function body\n\"after_lambda2\",\n perform(list(op(\"assign_symbol_value\"),\n constant(\"factorial\"),\n reg(\"val\"),\n reg(\"env\"))),\n assign(\"val\", constant(undefined))\n```\n\nA\nfunction\nbody is always compiled (by\ncompile_lambda_body)\nwith target and linkage\n\"next\".\n\nThe\nbody\nin this case consists of\na single\nreturn statement:\n\n```javascript\nreturn n === 1\n ? 1\n : factorial(n - 1) * n;\n```\n\nThe function compile_return_statement generates code to revert the stack using the marker and to restore the continue register, and then compiles the return expression with target and linkage , because its value is to be returned from the function.\n\nThe return expression is a conditional expression, for which compile_conditional\ngenerates code that first computes the predicate (targeted to\n), then checks the result and branches\naround the true branch if the predicate is false.\n\nRegisters\nand\nare preserved around the predicate code, since they may be needed for the\nrest of the\nconditional\nexpression.\n\nThe\ntrue and false branches are both\ncompiled with target and linkage\n\"return\".", + "token_count": 288, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "An Example of Compiled Code", "chunk_index": 2, - "chunk_id": "Computing_with_Register_Machines_Designing_Register_Machines_2" + "chunk_id": "Computing_with_Register_Machines_An_Example_of_Compiled_Code_2" }, { - "content": "In section , we will show how to implement a\nJavaScript\nevaluator as a register machine.\n\nIn order to simplify the discussion, we\nwill assume that our register machines can be equipped with a\nlist-structured memory , in which the basic operations for\nmanipulating list-structured data are primitive.\n\nPostulating the existence\nof such a memory is a useful abstraction when one is focusing on the\nmechanisms of control in\nan\ninterpreter, but this does not reflect a realistic view of the actual\nprimitive data operations of contemporary computers.\n\nTo obtain a more\ncomplete picture of how\n\n```javascript\nsystems can support list-structured memory\n efficiently,\n```\n\nwe must investigate how list structure can be represented in a way that is compatible with conventional computer memories.\n\nThere are two considerations in implementing list structure.\n\nThe first is\npurely an issue of representation: how to represent the\nbox-and-pointer structure of\npairs, using only the storage and addressing capabilities of typical computer\nmemories.\n\nThe second issue concerns the management of memory as a\ncomputation proceeds.\n\nThe operation of a\nJavaScript\nsystem depends crucially on the ability to\ncontinually create new data objects.\n\nThese include objects that are\nexplicitly created by the\nJavaScript\nfunctions\nbeing interpreted as well as structures created by the interpreter itself,\nsuch as environments and argument lists.\n\nAlthough the constant creation of\nnew data objects would pose no problem on a computer with an infinite amount\nof rapidly addressable memory, computer memories are available only in\nfinite sizes (more s the pity).\n\nJavaScript\nthus provide an\nautomatic storage allocation facility to\nsupport the illusion of an infinite memory.\n\nWhen a data object is no longer\nneeded, the memory allocated to it is automatically recycled and used to\nconstruct new data objects.\n\nThere are various techniques for providing such\nautomatic storage allocation.", + "content": "The\ntrue and false branches are both\ncompiled with target and linkage\n\"return\".\n\n```javascript\n$\\texttt{ }\\texttt{ }$revert_stack_to_marker(),\n restore(\"continue\"),\n save continue, env if modified by predicate and needed by branches\n compilation of predicate, target val, linkage \"next\"\n restore continue, env if saved above\n test(list(op(\"is_falsy\"), reg(\"val\"))),\n branch(label(\"false_branch4\")),\n\"true_branch3\",\n compilation of true branch, target val, linkage \"return\"\n\"false_branch4\",\n compilation of false branch, target val, linkage \"return\"\n\"after_cond5\",\n```\n\nThe predicate\nn === 1\nis a\nfunction application (after transformation of the operator combination).\n\nThis looks up the\nfunction expression (the symbol \"===\")\nand places this value in\nfun.\n\nIt then assembles the arguments and the value\nof into.\n\nThen it tests whether\nfun\ncontains a primitive or a compound\nfunction,\nand dispatches to a primitive branch or a compound branch accordingly.\n\nBoth branches resume at the\nafter_call\nlabel.\n\nThe compound branch must set up continue to jump past the primitive branch and push a marker to the stack to match the revert operation in the compiled return statement of the function.\n\nThe requirements to preserve registers around the evaluation of the\nfunction and argument expressions\ndon t result in\nany saving of registers, because in this case those evaluations don t\nmodify the registers in question.\n\n```javascript\n$\\texttt{ }\\texttt{ }$assign(\"fun\", list(op(\"lookup_symbol_value\"),\n constant(\"===\"), reg(\"env\"))),\n assign(\"val\", constant(1)),\n assign(\"argl\", list(op(\"list\"), reg(\"val\"))),\n assign(\"val\", list(op(\"lookup_symbol_value\"),\n constant(\"n\"), reg(\"env\"))),\n assign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))),\n test(list(op(\"is_primitive_function\"), reg(\"fun\"))),\n branch(label(\"primitive_branch6\")),\n\"compiled_branch7\",\n assign(\"continue\", label(\"after_call8\")),\n save(\"continue\"),\n push_marker_to_stack(),\n assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))),\n go_to(reg(\"val\")),\n\"primitive_branch6\",\n assign(\"val\", list(op(\"apply_primitive_function\"),\n reg(\"fun\"),\n reg(\"argl\"))),\n\"after_call8\",\n```\n\nThe true branch, which is the constant 1, compiles (with target and linkage \"return\") to\n\n```javascript\n$\\texttt{ }\\texttt{ }$assign(\"val\", constant(1)),\n go_to(reg(\"continue\")),\n```\n\nThe code for the false branch is another\nfunction\ncall, where the\nfunction\nis the value of the symbol\n\"*\",\nand the arguments\nare and the result of another\nfunction\ncall (a call to ).", "token_count": 299, "has_code": true, "chapter": "Computing with Register Machines", - "section": "Storage Allocation and Garbage Collection", - "subsection": null, - "chunk_index": 1, - "chunk_id": "Computing_with_Register_Machines_Storage_Allocation_and_Garbage_Collection_1" + "section": "Compilation", + "subsection": "An Example of Compiled Code", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_An_Example_of_Compiled_Code_3" }, { - "content": "There are various techniques for providing such\nautomatic storage allocation.\n\nThe method we shall discuss in this section\nis called garbage collection.", - "token_count": 22, + "content": "The code for the false branch is another\nfunction\ncall, where the\nfunction\nis the value of the symbol\n\"*\",\nand the arguments\nare and the result of another\nfunction\ncall (a call to ).\n\nFigure\nshows the complete compilation of the\ndeclaration\nof the\nfunction.\n\nNotice that the possible and\nof\nand\naround the predicate, shown above,\nare in fact generated, because these registers are modified by the\nfunction\ncall in the predicate and needed for the\nfunction\ncall and the\n\"return\"\nlinkage in the branches.", + "token_count": 87, "has_code": false, "chapter": "Computing with Register Machines", - "section": "Storage Allocation and Garbage Collection", - "subsection": null, + "section": "Compilation", + "subsection": "An Example of Compiled Code", + "chunk_index": 4, + "chunk_id": "Computing_with_Register_Machines_An_Example_of_Compiled_Code_4" + }, + { + "content": "This section describes the details on how instruction sequences are\nrepresented and combined.\n\nRecall from\nsection that an instruction\nsequence is represented as a list of the registers needed, the registers\nmodified, and the actual instructions.\n\nWe will also consider a label\n(string)\nto be a degenerate case of an instruction sequence, which\ndoesn t need or modify any registers.\n\nSo to determine the registers needed\nand modified by instruction sequences we use the selectors\n\n```javascript\nregisters_needed\n\nfunction registers_needed(s) {\n return is_string(s) ? null : head(s);\n}\nfunction registers_modified(s) {\n return is_string(s) ? null : head(tail(s));\n}\nfunction instructions(s) {\n return is_string(s) ? list(s) : head(tail(tail(s)));\n}\n```\n\nand to determine whether a given sequence needs or modifies a given register we use the predicates\n\n```javascript\nneeds_register\n\nfunction needs_register(seq, reg) {\n return ! is_null(member(reg, registers_needed(seq)));\n}\nfunction modifies_register(seq, reg) {\n return ! is_null(member(reg, registers_modified(seq)));\n}\n```\n\nIn terms of these predicates and selectors, we can implement the various instruction sequence combiners used throughout the compiler.\n\nThe basic combiner is\nappend_instruction_sequences.\n\nThis takes as\narguments\ntwo\ninstruction sequences that are to be\nexecuted sequentially and returns an instruction sequence whose statements\nare the statements of\nthe two\nsequences appended together.\n\nThe subtle point is to determine the registers that are needed and modified by the resulting sequence.\n\nIt modifies those registers that\nare modified by either sequence;\nit needs those registers that must be initialized before the\nfirst sequence can be run (the registers needed by the first sequence), together with those registers needed by\nthe second sequence that are not initialized (modified) by the first sequence.", + "token_count": 266, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Combining Instruction Sequences", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Combining_Instruction_Sequences_1" + }, + { + "content": "It modifies those registers that\nare modified by either sequence;\nit needs those registers that must be initialized before the\nfirst sequence can be run (the registers needed by the first sequence), together with those registers needed by\nthe second sequence that are not initialized (modified) by the first sequence.\n\n(In terms of set operations, the new set\nof needed registers is the union of the set of registers needed by\nwith the set difference of the registers\nneeded by and the registers modified by.) Thus,\nappend_instruction_sequences\nis implemented as follows:\n\n```javascript\nappend_instruction_sequences\n\nfunction append_instruction_sequences(seq1, seq2) {\n return make_instruction_sequence(\n list_union(registers_needed(seq1),\n list_difference(registers_needed(seq2),\n registers_modified(seq1))),\n list_union(registers_modified(seq1),\n registers_modified(seq2)),\n append(instructions(seq1), instructions(seq2)));\n}\n```\n\nThis function uses some simple operations for manipulating sets represented as lists, similar to the (unordered) set representation described in section :\n\n```javascript\nlist_union\n\nfunction list_union(s1, s2) {\n return is_null(s1)\n ? s2\n : is_null(member(head(s1), s2))\n ? pair(head(s1), list_union(tail(s1), s2))\n : list_union(tail(s1), s2);\n}\nfunction list_difference(s1, s2) {\n return is_null(s1)\n ? null\n : is_null(member(head(s1), s2))\n ? pair(head(s1), list_difference(tail(s1), s2))\n : list_difference(tail(s1), s2);\n}\n```\n\nThe function preserving,\nthe second major instruction\nsequence combiner, takes a list of registers\nand two instruction sequences\nand that\nare to be executed sequentially.\n\nIt returns an instruction sequence whose\ninstructions\nare the\ninstructions\nof followed\nby the\ninstructions\nof , with appropriate\nand\ninstructions around to protect the\nregisters in that are modified by\nbut needed by.\n\nTo accomplish this,\nfirst creates a sequence that has\nthe required s followed by the\ninstructions\nof followed by the required\ns.\n\nThis sequence needs the registers\nbeing saved and restored in addition to the registers needed by\n, and modifies the registers modified by\nexcept for the ones being saved and\nrestored.\n\nThis augmented sequence and\nare then appended in the usual way.", + "token_count": 294, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Combining Instruction Sequences", "chunk_index": 2, - "chunk_id": "Computing_with_Register_Machines_Storage_Allocation_and_Garbage_Collection_2" + "chunk_id": "Computing_with_Register_Machines_Combining_Instruction_Sequences_2" }, { - "content": "The representation method outlined in\nsection solves the problem of\nimplementing list structure, provided that we have an infinite amount of\nmemory.\n\nWith a real computer we will eventually run out of free space in\nwhich to construct new pairs. they are\ngarbage.\n\nFor instance, the computation\n\n```javascript\naccumulate((x, y) => x + y,\n 0,\n filter(is_odd, enumerate_interval(0, n)))\n```\n\nconstructs two lists: the enumeration and the result of filtering\nthe enumeration.\n\nWhen the accumulation is complete, these lists are\nno longer needed, and the allocated memory can be reclaimed.\n\nIf we\ncan arrange to collect all the garbage periodically, and if this turns\nout to recycle memory at about the same rate at which we construct new\npairs, we will have preserved the illusion that there is an infinite\namount of memory.\n\nIn order to recycle pairs, we must have a way to determine which\nallocated pairs are not needed (in the sense that their contents can\nno longer influence the future of the computation).\n\nThe method we\nshall examine for accomplishing this is known as garbage\ncollection.\n\nGarbage collection is based on the observation that, at\nany moment in\nan interpretation based on list-structured memory,\nthe only objects that can\naffect the future of the computation are those that can be reached by\nsome succession of\nhead\nand\ntail\noperations starting from the pointers that are currently in the machine\nregisters.\n\nThere are many ways to perform garbage collection.\n\nThe method we\nshall examine here is called\nstop-and-copy.\n\nThe basic idea is to divide memory into two\nhalves: working memory and free memory.\n\nWhen\npair\nconstructs pairs, it allocates these in working memory.\n\nWhen working memory\nis full, we perform garbage collection by locating all the useful pairs in\nworking memory and copying these into consecutive locations in free memory.", - "token_count": 301, + "content": "This augmented sequence and\nare then appended in the usual way.\n\n```javascript\npreserving\n\nfunction preserving(regs, seq1, seq2) {\n if (is_null(regs)) {\n return append_instruction_sequences(seq1, seq2);\n } else {\n const first_reg = head(regs);\n return needs_register(seq2, first_reg) &&\n modifies_register(seq1, first_reg)\n ? preserving(tail(regs),\n make_instruction_sequence(\n list_union(list(first_reg),\n registers_needed(seq1)),\n list_difference(registers_modified(seq1),\n list(first_reg)),\n append(list(save(first_reg)),\n append(instructions(seq1),\n list(restore(first_reg))))),\n seq2)\n : preserving(tail(regs), seq1, seq2);\n }\n}\n```\n\nAnother sequence combiner,\ntack_on_instruction_sequence,\nis used by\ncompile_lambda_expression\nto append a\nfunction\nbody to another sequence.\n\nBecause the\nfunction\nbody is not in line to be executed as part of the combined\nsequence, its register use has no impact on the register use of the sequence\nin which it is embedded.\n\nWe thus ignore the\nfunction\nbody s sets of needed and modified\nregisters when we tack it onto the other sequence.\n\n```javascript\ntack_on_instruction_sequence\n\nfunction tack_on_instruction_sequence(seq, body_seq) {\n return make_instruction_sequence(\n registers_needed(seq),\n registers_modified(seq),\n append(instructions(seq), instructions(body_seq)));\n}\n```\n\nThe functions compile_conditional\nand\ncompile_function_call\nuse a special combiner called\nparallel_instruction_sequences\nto append the two alternative branches that follow a test.\n\nThe two branches\nwill never be executed sequentially; for any particular evaluation of the\ntest, one branch or the other will be entered.\n\nBecause of this, the\nregisters needed by the second branch are still needed by the combined\nsequence, even if these are modified by the first branch.\n\n```javascript\nparallel_instruction_sequences\n\nfunction parallel_instruction_sequences(seq1, seq2) {\n return make_instruction_sequence(\n list_union(registers_needed(seq1),\n registers_needed(seq2)),\n list_union(registers_modified(seq1),\n registers_modified(seq2)),\n append(instructions(seq1), instructions(seq2)));\n}\n```", + "token_count": 227, "has_code": true, "chapter": "Computing with Register Machines", - "section": "Storage Allocation and Garbage Collection", - "subsection": "Maintaining the Illusion of Infinite Memory", + "section": "Compilation", + "subsection": "Combining Instruction Sequences", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_Combining_Instruction_Sequences_3" + }, + { + "content": "The essence of the compilation process is the compilation of\nfunction\napplications.\n\nThe code for\nan application\ncompiled with a given target and\nlinkage has the form\n\n```javascript\ncompilation of function expression, target fun, linkage \"next\"\nevaluate argument expressions and construct argument list in argl\ncompilation of function call with given target and linkage\n```\n\nThe registers ,\n,\nand may\nhave to be saved and restored during evaluation of the\nfunction and argument expressions.\n\nNote that this is the only place in the compiler where a target other\nthan is specified.\n\nThe required code is generated by\ncompile_application.\n\nThis recursively compiles the\nfunction expression,\nto produce code that puts the\nfunction\nto be applied into\nfun,\nand compiles the\nargument expressions,\nto produce code that evaluates the individual\nargument expressions\nof the\napplication.\n\nThe instruction sequences for the\nargument expressions\nare combined\n(by\nconstruct_arglist)\nwith code that constructs the list of\narguments in , and the resulting\nargument-list code is combined with the\nfunction\ncode and the code that performs the\nfunction\ncall (produced by\ncompile_function_call).\n\nIn appending the code sequences, the\nregister must be preserved around the evaluation of the\nfunction expression\n(since\nevaluating the\nfunction expression\nmight modify , which\nwill be needed to evaluate the\nargument expressions),\nand the\nregister must be preserved around the\nconstruction of the argument list (since evaluating the\nargument expressions\nmight\nmodify\n,\nwhich will be needed for the actual\nfunction\napplication).\n\nThe register\nmust also be preserved throughout, since\nit is needed for the linkage in the\nfunction\ncall.\n\n```javascript\ncompile_application\n\nfunction compile_application(exp, target, linkage) {\n const fun_code = compile(function_expression(exp), \"fun\", \"next\");\n const argument_codes = map(arg => compile(arg, \"val\", \"next\"),\n arg_expressions(exp));\n return preserving(list(\"env\", \"continue\"),\n fun_code,\n preserving(list(\"fun\", \"continue\"),\n construct_arglist(argument_codes),\n compile_function_call(target, linkage)));\n}\n```", + "token_count": 292, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Compiling Applications and Return Statements", "chunk_index": 1, - "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_1" + "chunk_id": "Computing_with_Register_Machines_Compiling_Applications_and_Return_Statements_1" }, { - "content": "When working memory\nis full, we perform garbage collection by locating all the useful pairs in\nworking memory and copying these into consecutive locations in free memory.\n\n(The useful pairs are located by tracing all the\nhead\nand\ntail\npointers, starting with the machine registers.) Since we do not copy the\ngarbage, there will presumably be additional free memory that we can\nuse to allocate new pairs.\n\nIn addition, nothing in the working memory\nis needed, since all the useful pairs in it have been copied.\n\nThus,\nif we interchange the roles of working memory and free memory, we can\ncontinue processing; new pairs will be allocated in the new working\nmemory (which was the old free memory).\n\nWhen this is full, we can\ncopy the useful pairs into the new free memory (which was the old\nworking memory).\n\nWe now use our register-machine language to describe the stop-and-copy\nalgorithm in more detail.\n\nWe will assume that there is a register\ncalled\nthe_heads\nand\nthe_tails,\nand the free memory is in registers called\nnew_heads\nand\nnew_tails.\n\nGarbage collection is triggered when we exhaust the free cells in the\ncurrent working memory, that is, when a\npair\noperation attempts to increment the new pairs will be constructed in the new memory,\nbeginning at the place indicated by\nFigure\nshows the arrangement of memory just before and just after garbage\ncollection.\n\nReconfiguration of memory by the garbage-collection process.\n\nThe state of the garbage-collection process is controlled by\nmaintaining two pointers:\nhead\nposition, we place a special tag that signals that this is an already-moved\nobject.\n\n(Such an object is traditionally called a\nbroken heart.)\ntail\nposition we place a\nforwarding address that points at the location to which the object\nhas been moved.", - "token_count": 291, - "has_code": false, + "content": "The register\nmust also be preserved throughout, since\nit is needed for the linkage in the\nfunction\ncall.\n\nSince we\nadjoin the arguments to the front of in sequence,\nwe must start with the last argument and end with the first, so that the\narguments will appear in order from first to last in the resulting list.\n\nRather than waste an instruction by initializing\nto the empty list\nto set up for this sequence of evaluations,\nwe make the first code sequence construct the initial.\n\nThe general form of the argument-list construction is thus as follows:\n\n```javascript\ncompilation of last argument, targeted to val\nassign(\"argl\", list(op(\"list\"), reg(\"val\"))),\ncompilation of next argument, targeted to val\nassign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))),\n$\\ldots$\ncompilation of first argument, targeted to val\nassign(\"argl\", list(op(\"pair\"), reg(\"val\"), reg(\"argl\"))),\n```\n\nThe register must be preserved around each argument evaluation except the first (so that arguments accumulated so far won t be lost), and must be\n\npreserved around each argument evaluation except the last (for use by subsequent argument evaluations).\n\nCompiling this argument code is a bit tricky, because of the special\ntreatment of the first\nargument expression\nto be evaluated and the need to preserve\nand in\ndifferent places.\n\nThe\nconstruct_arglist\nfunction\ntakes as arguments the code that evaluates the individual\nargument expressions.\n\nIf there are no\nargument expressions\nat all, it simply emits the instruction\n\n```javascript\nassign(argl, constant(null))\n```\n\nOtherwise,\nconstruct_arglist\ncreates code that initializes with the\nlast argument, and appends code that evaluates the rest of the arguments and\nadjoins them to in succession.\n\nIn order\nto process the arguments from last to first, we must reverse the list of\nargument\ncode sequences from the order supplied by\ncompile_application.", + "token_count": 282, + "has_code": true, "chapter": "Computing with Register Machines", - "section": "Storage Allocation and Garbage Collection", - "subsection": "Maintaining the Illusion of Infinite Memory", + "section": "Compilation", + "subsection": "Compiling Applications and Return Statements", "chunk_index": 2, - "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_2" + "chunk_id": "Computing_with_Register_Machines_Compiling_Applications_and_Return_Statements_2" }, { - "content": "(Such an object is traditionally called a\nbroken heart.)\ntail\nposition we place a\nforwarding address that points at the location to which the object\nhas been moved.\n\nAfter relocating the root, the garbage collector enters its basic\ncycle.\n\nAt each step in the algorithm, the\nhead\nand\ntail\npointers still refer to objects in the old memory.\n\nThese objects are each\nrelocated, and the\nhead\npointer of the pair we are scanning) we check to see if the object has\nalready been moved (as indicated by the presence of a broken-heart tag\nin the\nhead\nposition of the object).\n\nIf the object has not\nalready been moved, we copy it to the place indicated by\ns old location, and update the pointer to the object (in this\nexample, the\nhead\npointer of the pair we are scanning) to point\nto the new location.\n\nIf the object has already been moved, its\nforwarding address (found in the\ntail\nposition of the broken heart) is substituted for the pointer in the pair\nbeing scanned.\n\nEventually, all accessible objects will have been moved and\nscanned, at which point the\n\nWe can specify the stop-and-copy algorithm as a sequence of instructions for\na register machine.\n\nThe basic step of relocating an object is accomplished\nby a subroutine called\nrelocate_old_result_in_new.\n\nThis subroutine gets its argument, a pointer to the object to be relocated,\nfrom a register named\nrelocate_continue.\n\nTo begin garbage collection, we invoke this subroutine to relocate the\n\n```javascript\nbegin_garbage_collection\n\n\"begin_garbage_collection\",\n assign(\"free\", constant(0)),\n assign(\"scan\", constant(0)),\n assign(\"old\", reg(\"root\")),\n assign(\"relocate_continue\", label(\"reassign_root\")),\n go_to(label(\"relocate_old_result_in_new\")),\n\"reassign_root\",\n assign(\"root\", reg(\"new\")),\n go_to(label(\"gc_loop\")),\n```\n\nIn the main loop of the garbage collector we must determine whether\nthere are any more objects to be scanned.\n\nWe do this by testing\nwhether the\ngc_flip,\nwhich cleans things up so that we can continue the interrupted computation.", - "token_count": 302, + "content": "In order\nto process the arguments from last to first, we must reverse the list of\nargument\ncode sequences from the order supplied by\ncompile_application.\n\nAfter evaluating the elements of a\nfunction application,\nthe compiled code must apply the\nfunction\nin\nto the arguments in.\n\nThe code performs essentially the same\ndispatch as the\nfunction\nin the metacircular evaluator of\nsection or the\napply_dispatch\nentry point in the explicit-control evaluator of\nsection.\n\nIt checks whether\nthe\nfunction\nto be applied is a primitive\nfunction\nor a compiled\nfunction.\n\nFor a primitive\nfunction,\nit uses\napply_primitive_function;\nwe will see shortly how it handles compiled\nfunctions.\n\nThe\nfunction-application\ncode has the following form:\n\n```javascript\n$\\texttt{ }\\texttt{ }$test(list(op(\"primitive_function\"), reg(\"fun\"))),\n branch(label(\"primitive_branch\")),\n\"compiled_branch\",\n code to apply compiled function with given target and appropriate linkage\n\"primitive_branch\",\n assign(target,\n list(op(\"apply_primitive_function\"), reg(\"fun\"), reg(\"argl\"))),\n linkage\n\"after_call\"\n```\n\nObserve that the compiled branch must skip around the primitive\nbranch.\n\nTherefore, if the linkage for the original\nfunction\ncall was\n\"next\",\nthe compound branch must use a\nlinkage that jumps to a label that is inserted after the primitive branch.\n\n(This is similar to the linkage used for the true branch in\ncompile_conditional.)\n\n```javascript\ncompile_function_call\n\nfunction compile_function_call(target, linkage) {\n const primitive_branch = make_label(\"primitive_branch\");\n const compiled_branch = make_label(\"compiled_branch\");\n const after_call = make_label(\"after_call\");\n const compiled_linkage = linkage === \"next\" ? after_call : linkage;\n return append_instruction_sequences(\n make_instruction_sequence(list(\"fun\"), null,\n list(test(list(op(\"is_primitive_function\"), reg(\"fun\"))),\n branch(label(primitive_branch)))),\n append_instruction_sequences(\n parallel_instruction_sequences(\n append_instruction_sequences(\n compiled_branch,\n compile_fun_appl(target, compiled_linkage)),\n append_instruction_sequences(\n primitive_branch,\n end_with_linkage(linkage,\n make_instruction_sequence(list(\"fun\", \"argl\"),\n list(target),\n list(assign(\n target,\n list(op(\"apply_primitive_function\"),\n reg(\"fun\"), reg(\"argl\")))))))),\n after_call));\n}\n```\n\nThe primitive and compound branches, like the true and false branches in compile_@conditional, are appended using parallel_instruction_sequences rather than the ordinary append_instruction_sequences, because they will\n\nnot be executed sequentially.\n\nThe handling of function\napplication\nand return\nis the most subtle part of the\ncompiler.", + "token_count": 290, "has_code": true, "chapter": "Computing with Register Machines", - "section": "Storage Allocation and Garbage Collection", - "subsection": "Maintaining the Illusion of Infinite Memory", + "section": "Compilation", + "subsection": "Compiling Applications and Return Statements", "chunk_index": 3, - "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_3" + "chunk_id": "Computing_with_Register_Machines_Compiling_Applications_and_Return_Statements_3" }, { - "content": "We do this by testing\nwhether the\ngc_flip,\nwhich cleans things up so that we can continue the interrupted computation.\n\nIf there are still pairs to be scanned, we call the relocate subroutine to\nrelocate the\nhead\nof the next pair (by placing the\nhead\npointer in\nrelocate_continue\nregister is set up so that the subroutine will return to update the\nhead\npointer.\n\n```javascript\ngc_loop\n\n\"gc_loop\",\n test(list(op(\"===\"), reg(\"scan\"), reg(\"free\"))),\n branch(label(\"gc_flip\")),\n assign(\"old\", list(op(\"vector_ref\"), reg(\"new_heads\"), reg(\"scan\"))),\n assign(\"relocate_continue\", label(\"update_head\")),\n go_to(label(\"relocate_old_result_in_new\")),\n```\n\nAt\nupdate_head,\nwe modify the\nhead\npointer of the pair being scanned, then proceed to relocate the\ntail\nof the pair.\n\nWe return to\nupdate_tail\nwhen that relocation has been accomplished.\n\nAfter relocating and updating\nthe\ntail,\nwe are finished scanning that pair, so we continue with the main loop.\n\n```javascript\nupdate_head\n\n\"update_head\",\n perform(list(op(\"vector_set\"),\n reg(\"new_heads\"), reg(\"scan\"), reg(\"new\"))),\n assign(\"old\", list(op(\"vector_ref\"),\n reg(\"new_tails\"), reg(\"scan\"))),\n assign(\"relocate_continue\", label(\"update_tail\")),\n go_to(label(\"relocate_old_result_in_new\")),\n\n\"update_tail\",\n perform(list(op(\"vector_set\"),\n reg(\"new_tails\"), reg(\"scan\"), reg(\"new\"))),\n assign(\"scan\", list(op(\"+\"), reg(\"scan\"), constant(1))),\n go_to(label(\"gc_loop\")),\n```\n\nThe subroutine\nrelocate_old_result_in_new\nrelocates objects as follows: If the object to be relocated (pointed at by\nhead\nis the number 4.\n\nIf we represent the\nhead\nby , then we want the\nrelocated\nhead\npointer to still be\nhead\nposition of the pair to be relocated contains a broken-heart tag, then the\npair has in fact already been moved, so we retrieve the forwarding address\n(from the\ntail\nposition of the broken heart) and return this in\n\n```javascript\nThe subroutine\n relocate_old_result_in_new\n```\n\nuses a register oldht to hold the head or the tail of the object pointed at by", - "token_count": 252, + "content": "The handling of function\napplication\nand return\nis the most subtle part of the\ncompiler.\n\nThe code at this entry point computes a result in\nand ends by executing the instructions from a compiled return statement.\n\nThe code for a compiled-function application uses the stack in the same way as the explicit-control evaluator (section ): before jumping to the compiled function's\n\nentry point, it saves the continuation of the function call to the stack, followed by a mark that allows reverting the stack to the state\n\nright before the call with the continuation on top.\n\n```javascript\n$\\texttt{ }\\texttt{ }$// set up for return from function\n save(\"continue\"),\n push_marker_to_stack(),\n // jump to the function's entry point\n assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))),\n go_to(reg(\"val\")),\n```\n\nCompiling a return statement (with compile_return_statement ) generates corresponding code for reverting the stack and restoring and jumping to continue.\n\n```javascript\n$\\texttt{ }\\texttt{ }$revert_stack_to_marker(),\n restore(\"continue\"),\n evaluate the return expression and store the result in val\n go_to(reg(\"continue\")), // $\\texttt{\"return\"}$-linkage code\n```\n\nUnless a function enters an infinite loop, it will end by executing the above return code, resulting from either a return statement in the program\n\nor one inserted by compile_@lambda_@body to return undefined.\n\nStraightforward code for a compiled-function application with a given target and linkage would set up continue to make the function return to a local label instead of to the final linkage, to copy the function value from val to the target register if necessary.\n\nIt would look like this if the linkage is a label:", + "token_count": 247, "has_code": true, "chapter": "Computing with Register Machines", - "section": "Storage Allocation and Garbage Collection", - "subsection": "Maintaining the Illusion of Infinite Memory", + "section": "Compilation", + "subsection": "Compiling Applications and Return Statements", "chunk_index": 4, - "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_4" + "chunk_id": "Computing_with_Register_Machines_Compiling_Applications_and_Return_Statements_4" }, { - "content": "uses a register oldht to hold the head or the tail of the object pointed at by\n\n```javascript\nrelocate_old_result_in_new\n\n\"relocate_old_result_in_new\",\n test(list(op(\"is_pointer_to_pair\"), reg(\"old\"))),\n branch(label(\"pair\")),\n assign(\"new\", reg(\"old\")),\n go_to(reg(\"relocate_continue\")),\n\"pair\",\n assign(\"oldht\", list(op(\"vector_ref\"),\n reg(\"the_heads\"), reg(\"old\"))),\n test(list(op(\"is_broken_heart\"), reg(\"oldht\"))),\n branch(label(\"already_moved\")),\n assign(\"new\", reg(\"free\")), // new location for pair\n // Update $\\texttt{free}$ pointer\n assign(\"free\", list(op(\"+\"), reg(\"free\"), constant(1))),\n // Copy the head and tail to new memory\n perform(list(op(\"vector_set\"),\n reg(\"new_heads\"), reg(\"new\"),\n reg(\"oldht\"))),\n assign(\"oldht\", list(op(\"vector_ref\"),\n reg(\"the_tails\"), reg(\"old\"))),\n perform(list(op(\"vector_set\"),\n reg(\"new_tails\"), reg(\"new\"),\n reg(\"oldht\"))),\n // Construct the broken heart\n perform(list(op(\"vector_set\"),\n reg(\"the_heads\"), reg(\"old\"),\n constant(\"broken_heart\"))),\n perform(list(op(\"vector_set\"),\n reg(\"the_tails\"), reg(\"old\"),\n reg(\"new\"))),\n go_to(reg(\"relocate_continue\")),\n\"already_moved\",\n assign(\"new\", list(op(\"vector_ref\"),\n reg(\"the_tails\"), reg(\"old\"))),\n go_to(reg(\"relocate_continue\")),\n```\n\nAt the very end of the garbage collection process, we interchange the\nrole of old and new memories by interchanging pointers: interchanging\nthe_heads\nwith\nnew_heads,\nand\nthe_tails\nwith\nnew_tails.\n\nWe will then be ready to perform another garbage\ncollection the next time memory runs out.", - "token_count": 136, + "content": "It would look like this if the linkage is a label:\n\nor like thissaving the callers continuation at the start in order to restore and go to it at the endif the linkage is (that is,\n\nif the application is in a return statement and its value is the result to be returned):\n\n```javascript\n$\\texttt{ }\\texttt{ }$save(\"continue\"), // save the caller's continuation\n assign(\"continue\", label(\"fun_return\")), // where function should return to\n save(\"continue\"), // will be restored by the function\n push_marker_to_stack(), // allows the function to revert stack to find $\\texttt{fun_return}$\n assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))),\n go_to(reg(\"val\")), // eventually reverts stack, restores and jumps to $\\texttt{continue}$\n\"fun_return\", // the function returns to here\n assign($\\mathit{target}$, reg(\"val\")), // included if target is not $\\texttt{val}$\n restore(\"continue\"), // restore the caller's continuation\n go_to(reg(\"continue\")), // linkage code\n```\n\nThis code sets up so that the\nfunction\nwill return to a label\nfun_return\nand jumps to the\nfunctions\nentry point.\n\nThe code at\nfun_return\ntransfers the\nfunctions\nresult from to the target register (if\nnecessary) and then jumps to the location specified by the linkage.\n\n(The linkage is always\n\"return\"\nor a label,\nbecause\ncompile_@function_@call\nreplaces a\n\"next\"\nlinkage for the\ncompound-function branch by an after_@call\nlabel.)\nBefore jumping to the function's entry point, we save continue and execute push_@marker_@to_@stack() to enable the function to return to the intended location in the program with the expected stack.\n\nMatching revert_@stack_@to_@marker() and restore(\"continue\") instructions are generated by compile_@return_@statement for each return statement in the body of the function.\n\nIn fact, if\nthe target is not ,\nthe above is\nexactly the code our compiler will generate.", + "token_count": 264, "has_code": true, "chapter": "Computing with Register Machines", - "section": "Storage Allocation and Garbage Collection", - "subsection": "Maintaining the Illusion of Infinite Memory", + "section": "Compilation", + "subsection": "Compiling Applications and Return Statements", "chunk_index": 5, - "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_5" + "chunk_id": "Computing_with_Register_Machines_Compiling_Applications_and_Return_Statements_5" }, { - "content": "We will then be ready to perform another garbage\ncollection the next time memory runs out.\n\n```javascript\ntesting_5_3_2\n tagged_list\n make_go_to\n pop\n lookup1\n go_to_go_to_dest\n make_register\n\n// TYPED POINTERS\n\nconst NUMBER_TYPE = \"number\";\nconst BOOL_TYPE = \"bool\";\nconst STRING_TYPE = \"string\";\nconst PTR_TYPE = \"ptr\";\nconst PROG_TYPE = \"prog\";\nconst NULL_TYPE = \"null\";\nconst UNDEFINED_TYPE = \"undefined\";\nconst NO_VALUE_YET_TYPE = \"*unassigned*\";\nconst BROKEN_HEART_TYPE = \"broken_heart\";\n\nfunction make_ptr_ptr(idx) {\n return pair(PTR_TYPE, idx);\n}\n\nfunction make_null_ptr() {\n return pair(NULL_TYPE, null);\n}\n\nfunction make_no_value_yet_ptr() {\n return pair(NO_VALUE_YET_TYPE, null);\n}\n\nfunction make_prog_ptr(idx) {\n return pair(PROG_TYPE, idx);\n}\n\nfunction make_broken_heart_ptr() {\n return pair(BROKEN_HEART_TYPE, null);\n}\n\nfunction get_elem_type(elem) {\n return is_number(elem) ? NUMBER_TYPE :\n is_boolean(elem) ? BOOL_TYPE :\n is_string(elem) ? STRING_TYPE :\n is_null(elem) ? NULL_TYPE :\n is_undefined(elem) ? UNDEFINED_TYPE :\n error(elem, \"invalid typed elem\");\n}\n\nfunction wrap_ptr(elem) {\n return pair(get_elem_type(elem), elem);\n}\n\nfunction unwrap_ptr(ptr) {\n return tail(ptr);\n}\n\nfunction is_ptr(ptr) {\n return is_pair(ptr) &&\n !is_pair(head(ptr)) &&\n !is_pair(tail(ptr)) &&\n (head(ptr) === NUMBER_TYPE ||\n head(ptr) === BOOL_TYPE ||\n head(ptr) === STRING_TYPE ||\n head(ptr) === PTR_TYPE ||\n head(ptr) === NULL_TYPE ||\n head(ptr) === UNDEFINED_TYPE ||\n head(ptr) === PROG_TYPE ||\n head(ptr) === NO_VALUE_YET_TYPE ||\n head(ptr) === BROKEN_HEART_TYPE);\n}\n\nfunction is_number_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === NUMBER_TYPE;\n}\n\nfunction is_bool_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === BOOL_TYPE;\n}\n\nfunction is_string_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === STRING_TYPE;\n}\n\nfunction is_ptr_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === PTR_TYPE;\n}\n\nfunction is_null_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === NULL_TYPE;\n}\n\nfunction is_undefined_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === UNDEFINED_TYPE;\n}\n\nfunction is_prog_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === PROG_TYPE;\n}\n\nfunction is_no_value_yet_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === NO_VALUE_YET_TYPE;\n}\n\nfunction is_broken_heart_ptr(ptr) {\n return is_ptr(ptr) && head(ptr) === BROKEN_HEART_TYPE;\n}\n\n// Primitive functions and constants\n\nconst primitive_function_names_arities = list(\n pair(\"display\", 1),\n pair(\"error\", 1),\n pair(\"+\", 2),\n pair(\"-\", 2),\n pair(\"*\", 2),\n pair(\"/\", 2),\n pair(\"%\", 2),\n pair(\"===\", 2),\n pair(\"!==\", 2),\n pair(\"<\", 2),\n pair(\"<=\", 2),\n pair(\">\", 2),\n pair(\">=\", 2),\n pair(\"!\", 1),\n pair(\"||\", 2),\n pair(\"&&\", 2)\n);\n\nconst primitive_constants = list(\n list(\"undefined\", undefined),\n list(\"math_PI\" , math_PI)\n );\n\nfunction make_primitive_function(impl) {\n return list(\"primitive\", impl);\n}\n\nfunction setup_environment() {\n const primitive_function_names =\n map(head, primitive_function_names_arities);\n const primitive_function_values =\n map(name => pair(make_primitive_function(name), false),\n primitive_function_names);\n const primitive_constant_names =\n map(head, primitive_constants);\n const primitive_constant_values =\n map(f => pair(head(tail(f)), false),\n primitive_constants);\n return pair(pair(\n append(primitive_function_names,\n primitive_constant_names),\n append(primitive_function_values,\n primitive_constant_values)),\n null);\n}\n\nfunction flatten_list_to_vectors(the_heads, the_tails, lst,\n make_ptr_fn, starting_index) {\n let free = starting_index;\n function helper(lst) {\n if (!is_pair(lst)) {\n return wrap_ptr(lst);\n } else {\n const index = free;\n free = free + 1;\n const elem = head(lst);\n the_heads[index] = helper(elem);\n the_tails[index] = helper(tail(lst));\n return make_ptr_fn(index);\n }\n }\n helper(lst);\n return free;\n}\n\n// MACHINE\nfunction get_contents(register) {\n return register(\"get\");\n}\n\nfunction set_contents(register, value) {\n return register(\"set\")(value);\n}\n\nfunction make_stack() {\n let stack = null;\n\n function push(x) {\n stack = pair(x, stack);\n return \"done\";\n }\n\n function pop() {\n if (is_null(stack)) {\n error(\"empty stack -- pop\");\n\n } else {\n const top = head(stack);\n stack = tail(stack);\n return top;\n }\n }\n\n function initialize() {\n stack = null;\n return \"done\";\n }\n\n function dispatch(message) {\n return message === \"push\"\n ? push\n : message === \"pop\"\n ? pop()\n : message === \"initialize\"\n ? initialize()\n : message === \"stack\"\n ? stack\n : error(message, \"unknown request -- stack\");\n }\n\n return dispatch;\n}\n\nfunction make_new_machine() {\n const SIZE = make_register(\"SIZE\");\n const pc = make_register(\"pc\");\n const flag = make_register(\"flag\");\n const stack = make_stack();\n const stack_reassign_proc = make_register(\"stack_reassign_proc\");\n const free = make_register(\"free\");\n const root = make_register(\"root\");\n const root_populate_proc = make_register(\"root_populate_proc\");\n const root_restore_proc = make_register(\"root_restore_proc\");\n const gc_registers = list(\n list(\"free\", free),\n list(\"scan\", make_register(\"scan\")),\n list(\"old\", make_register(\"old\")),\n list(\"new\", make_register(\"new\")),\n list(\"relocate_continue\", make_register(\"relocate_continue\")),\n list(\"temp\", make_register(\"temp\")),\n list(\"oldht\", make_register(\"oldht\"))\n );\n const exp = make_register(\"exp\");\n const env = make_register(\"env\");\n const evaluator_registers = list(\n list(\"exp\", exp),\n list(\"env\", env),\n list(\"val\", make_register(\"val\")),\n list(\"continue\", make_register(\"continue\")),\n list(\"fun\", make_register(\"fun\")),\n list(\"argl\", make_register(\"argl\")),\n list(\"unev\", make_register(\"unev\")),\n list(\"fun\", make_register(\"fun\"))\n );\n const aux_registers = list(\n list(\"res\", make_register(\"val\")),\n list(\"err\", make_register(\"err\")),\n list(\"a\", make_register(\"a\")),\n list(\"b\", make_register(\"b\")),\n list(\"c\", make_register(\"c\")),\n list(\"d\", make_register(\"d\")),\n list(\"e\", make_register(\"e\")),\n list(\"f\", make_register(\"f\"))\n );\n const the_heads = make_register(\"the_heads\");\n const the_tails = make_register(\"the_tails\");\n set_contents(the_heads, make_vector());\n set_contents(the_tails, make_vector());\n const new_heads = make_register(\"new_heads\");\n const new_tails = make_register(\"new_tails\");\n set_contents(new_heads, make_vector());\n set_contents(new_tails, make_vector());\n const prog_heads = make_register(\"prog_heads\");\n const prog_tails = make_register(\"prog_tails\");\n let the_instruction_sequence = null;\n let the_ops = list(list(\"initialize_stack\",\n () => stack(\"initialize\")));\n the_ops = append(the_ops, vector_ops);\n let register_table =\n list(list(\"SIZE\", SIZE),\n list(\"pc\", pc),\n list(\"flag\", flag),\n list(\"root\", root),\n list(\"root_populate_proc\", root_populate_proc),\n list(\"root_restore_proc\", root_restore_proc),\n list(\"stack_reassign_proc\", stack_reassign_proc),\n list(\"the_heads\", the_heads),\n list(\"the_tails\", the_tails),\n list(\"new_heads\", new_heads),\n list(\"new_tails\", new_tails),\n list(\"prog_heads\", prog_heads),\n list(\"prog_tails\", prog_tails));\n register_table = append(register_table, gc_registers);\n register_table = append(register_table, evaluator_registers);\n register_table = append(register_table, aux_registers);\n\n function start() {\n const root_registers =\n append(aux_registers, evaluator_registers);\n set_contents(pc, the_instruction_sequence);\n set_contents(free,\n make_ptr_ptr(flatten_list_to_vectors(\n the_heads(\"get\"),\n the_tails(\"get\"),\n setup_environment(),\n make_ptr_ptr,\n length(root_registers))));\n set_contents(env, make_ptr_ptr(length(root_registers)));\n function root_populate_proc_fn() {\n const root_ptr = free(\"get\");\n root(\"set\")(root_ptr);\n let register_list = root_registers;\n while (!is_null(register_list)) {\n const content = head(tail(head(register_list)))(\"get\");\n const index = unwrap_ptr(free(\"get\"));\n the_heads(\"get\")[index] =\n content === \"*unassigned*\"\n ? make_null_ptr() : content;\n free(\"set\")(make_ptr_ptr(index + 1));\n the_tails(\"get\")[index] = free(\"get\");\n register_list = tail(register_list);\n }\n the_tails(\"get\")[unwrap_ptr(free(\"get\")) - 1] =\n make_null_ptr();\n }\n function root_restore_proc_fn() {\n let root_ptr = root(\"get\");\n let register_list = root_registers;\n while (!is_null(register_list)) {\n const index = unwrap_ptr(root_ptr);\n const value = the_heads(\"get\")[index];\n head(tail(head(register_list)))(\"set\")(value);\n root_ptr = the_tails(\"get\")[index];\n register_list = tail(register_list);\n }\n }\n function stack_reassign_proc_fn() {\n let local_stack = stack(\"stack\");\n while (!is_null(local_stack)) {\n const value = head(local_stack);\n if (is_ptr_ptr(value)) {\n const index = unwrap_ptr(value);\n const new_ptr = the_tails(\"get\")[index];\n set_head(local_stack, new_ptr);\n } else {}\n local_stack = tail(local_stack);\n }\n }\n set_contents(root_populate_proc, root_populate_proc_fn);\n set_contents(root_restore_proc, root_restore_proc_fn);\n set_contents(stack_reassign_proc, stack_reassign_proc_fn);\n return execute();\n }\n function allocate_register(name) {\n if (is_undefined(assoc(name, register_table))) {\n register_table = pair(list(name, make_register(name)),\n register_table);\n } else {\n error(name, \"multiply defined register\");\n }\n return \"register allocated\";\n }\n function lookup_register(name) {\n const val = assoc(name, register_table);\n return is_undefined(val)\n ? error(name, \"unknown register\")\n : head(tail(val));\n }\n function execute() {\n const insts = get_contents(pc);\n if (is_null(insts)) {\n return \"done\";\n } else {\n const proc = inst_execution_fun(head(insts));\n proc();\n return execute();\n }\n }\n function dispatch(message) {\n return message === \"start\"\n ? start\n : message === \"install_instruction_sequence\"\n ? seq => { the_instruction_sequence = seq; }\n : message === \"allocate_register\"\n ? allocate_register\n : message === \"get_register\"\n ? lookup_register\n : message === \"install_operations\"\n ? ops => { the_ops = append(the_ops, ops); }\n : message === \"stack\"\n ? stack\n : message === \"operations\"\n ? the_ops\n : error(message, \"unknown request -- machine\");\n }\n return dispatch;\n}\n\nfunction make_machine(register_names, ops, controller) {\n const machine = make_new_machine();\n\n map(reg_name => machine(\"allocate_register\")(reg_name), register_names);\n machine(\"install_operations\")(ops);\n machine(\"install_instruction_sequence\")(assemble(controller, machine));\n\n return machine;\n}\n\nfunction start(machine) {\n return machine(\"start\")();\n}\n\nfunction get_register_contents(machine, register_name) {\n return get_contents(get_register(machine, register_name));\n}\n\nfunction set_register_contents(machine, register_name, value) {\n set_contents(get_register(machine, register_name), value);\n return \"done\";\n}\n\nfunction get_register(machine, reg_name) {\n return machine(\"get_register\")(reg_name);\n}\n\n// ASSEMBLER\n\nfunction assemble(controller, machine) {\n function receive(insts, labels) {\n update_insts(insts, labels, machine);\n return insts;\n }\n\n return extract_labels(controller, receive);\n}\n\nfunction extract_labels(text, receive) {\n function helper(insts, labels) {\n const next_inst = head(text);\n\n return is_string(next_inst)\n ? receive(insts, pair(make_label_entry(next_inst, insts), labels))\n : receive(pair(make_inst(next_inst), insts), labels);\n }\n\n return is_undefined(text) || is_null(text)\n ? receive(null, null)\n : extract_labels(tail(text), helper);\n}\n\nfunction update_insts(insts, labels, machine) {\n const pc = get_register(machine, \"pc\");\n const flag = get_register(machine, \"flag\");\n const stack = machine(\"stack\");\n const ops = machine(\"operations\");\n\n const set_iep = set_inst_execution_fun;\n const make_ep = make_execution_function;\n return map(i => set_iep(i,\n make_ep(inst_controller_instruction(i),\n labels,\n machine,\n pc,\n flag,\n stack,\n ops)),\n insts);\n}\n\nfunction make_inst(inst_controller_instruction) {\n return pair(inst_controller_instruction, null);\n}\n\nfunction inst_controller_instruction(inst) {\n return head(inst);\n}\n\nfunction inst_execution_fun(inst) {\n return tail(inst);\n}\n\nfunction set_inst_execution_fun(inst, fun) {\n set_tail(inst, fun);\n}\n\nfunction make_label_entry(label_name, insts) {\n return pair(label_name, insts);\n}\n\nfunction lookup_label(labels, label_name) {\n const val = assoc(label_name, labels);\n\n return is_undefined(val)\n ? error(label_name, \"undefined label -- assemble\")\n : tail(val);\n}\n\nfunction make_execution_function(inst, labels, machine, pc, flag, stack, ops) {\n const x = head(inst);\n\n return x === \"assign\"\n ? make_assign_ef(inst, machine, labels, ops, pc)\n : x === \"test\"\n ? make_test_ef(inst, machine, labels, ops, flag, pc)\n : x === \"branch\"\n ? make_branch_ef(inst, machine, labels, flag, pc)\n : x === \"go_to\"\n ? make_go_to_ef(inst, machine, labels, pc)\n : x === \"save\"\n ? make_save_ef(inst, machine, stack, pc)\n : x === \"restore\"\n ? make_restore_ef(inst, machine, stack, pc)\n : x === \"perform\"\n ? make_perform_ef(inst, machine, labels, ops, pc)\n : x === \"dump_memory\" // Added to allow printing the memory vectors\n ? () => {\n display(stringify(get_register_contents(machine, \"the_heads\")));\n display(stringify(get_register_contents(machine, \"the_tails\")));\n advance_pc(pc);\n }\n : error(inst, \"unknown instruction type -- assemble\");\n}\n\nfunction make_assign_ef(inst, machine, labels, operations, pc) {\n const target = get_register(machine, assign_reg_name(inst));\n const value_exp = assign_value_exp(inst);\n const value_fun = is_operation_exp(value_exp)\n ? make_operation_exp_ef(value_exp, machine, labels, operations)\n : make_primitive_exp_ef(value_exp, machine, labels);\n\n function perform_assign() {\n set_contents(target, value_fun());\n advance_pc(pc);\n }\n\n return perform_assign;\n}\n\nfunction assign_reg_name(assign_instruction) {\n return head(tail(assign_instruction));\n}\n\nfunction assign_value_exp(assign_instruction) {\n return head(tail(tail(assign_instruction)));\n}\n\nfunction assign(reg_name, value_exp) {\n return list(\"assign\", reg_name, value_exp);\n}\n\nfunction dump_memory() {\n return list(\"dump_memory\", \"the_heads\", \"the_tails\");\n}\n\nfunction advance_pc(pc) {\n set_contents(pc, tail(get_contents(pc)));\n\n}\n\nfunction make_test_ef(inst, machine, labels, operations, flag, pc) {\n const condition = test_condition(inst);\n\n if (is_operation_exp(condition)) {\n const condition_fun = make_operation_exp_ef(condition, machine,\n labels, operations);\n\n function perform_test() {\n set_contents(flag, unwrap_ptr(condition_fun()));\n advance_pc(pc);\n }\n\n return perform_test;\n } else {\n error(inst, \"bad test instruction -- assemble\");\n }\n}\n\nfunction test_condition(test_instruction) {\n return head(tail(test_instruction));\n}\n\nfunction test(condition) {\n return list(\"test\", condition);\n}\n\nfunction make_branch_ef(inst, machine, labels, flag, pc) {\n const dest = branch_dest(inst);\n\n if (is_label_exp(dest)) {\n const insts = lookup_label(labels, label_exp_label(dest));\n\n function perform_branch() {\n if (get_contents(flag)) {\n set_contents(pc, insts);\n\n } else {\n advance_pc(pc);\n }\n }\n\n return perform_branch;\n\n } else {\n error(inst, \"bad branch instruction -- assemble\");\n }\n}\n\nfunction branch_dest(branch_instruction) {\n return head(tail(branch_instruction));\n}\n\nfunction branch(dest) {\n return list(\"branch\", dest);\n}\n\nfunction make_goto(inst, machine, labels, pc) {\n const dest = goto_dest(inst);\n\n if (is_label_exp(dest)) {\n const insts = lookup_label(labels, label_exp_label(dest));\n return () => set_contents(pc, insts);\n\n } else if (is_register_exp(dest)) {\n const reg = get_register(machine, register_exp_reg(dest));\n return () => set_contents(pc, get_contents(reg));\n\n } else {\n error(inst, \"bad go_to instruction -- assemble\");\n }\n}\n\nfunction goto_dest(goto_instruction) {\n return head(tail(goto_instruction));\n}\n/*\nfunction go_to(dest) {\n return list(\"go_to\", dest);\n}\n*/\nfunction make_save_ef(inst, machine, stack, pc) {\n const reg = get_register(machine, stack_inst_reg_name(inst));\n\n function perform_save() {\n push(stack, get_contents(reg));\n advance_pc(pc);\n }\n\n return perform_save;\n}\n\nfunction make_restore_ef(inst, machine, stack, pc) {\n const reg = get_register(machine, stack_inst_reg_name(inst));\n\n function perform_restore() {\n set_contents(reg, pop(stack));\n advance_pc(pc);\n }\n\n return perform_restore;\n}\n\nfunction stack_inst_reg_name(stack_instruction) {\n return head(tail(stack_instruction));\n}\n\nfunction save(register_name) {\n return list(\"save\", register_name);\n}\n\nfunction restore(register_name) {\n return list(\"restore\", register_name);\n}\n\nfunction make_perform_ef(inst, machine, labels, operations, pc) {\n const action = perform_action(inst);\n\n if (is_operation_exp(action)) {\n const action_fun = make_operation_exp_ef(action, machine,\n labels, operations);\n return () => { action_fun(); advance_pc(pc); };\n\n } else {\n error(inst, \"bad perform instruction -- assemble\");\n }\n}\n\nfunction perform_action(inst) {\n return head(tail(inst));\n}\n\nfunction perform(op) {\n return list(\"perform\", op);\n}\n\nfunction make_primitive_exp_ef(exp, machine, labels) {\n if (is_constant_exp(exp)) {\n const c = constant_exp_value(exp);\n return () => c;\n\n } else if (is_label_exp(exp)) {\n const insts = lookup_label(labels, label_exp_label(exp));\n return () => insts;\n\n } else if (is_register_exp(exp)) {\n const r = get_register(machine, register_exp_reg(exp));\n return () => get_contents(r);\n\n } else {\n error(exp, \"unknown expression type -- assemble\");\n }\n}\n\n/* TODO: probably remove these -- suddenly available through new import chains\nfunction is_register_exp(exp) {\n return is_tagged_list(exp, \"reg\");\n}\n\nfunction register_exp_reg(exp) {\n return head(tail(exp));\n}\n\nfunction reg(name) {\n return list(\"reg\", name);\n}\n\nfunction is_constant_exp(exp) {\n return is_tagged_list(exp, \"constant\");\n}\n\nfunction constant_exp_value(exp) {\n return head(tail(exp));\n}\n\nfunction constant(value) {\n return list(\"constant\", wrap_ptr(value));\n}\n\nfunction is_label_exp(exp) {\n return is_tagged_list(exp, \"label\");\n}\n\nfunction label_exp_label(exp) {\n return head(tail(exp));\n}\n\nfunction label(string) {\n return list(\"label\", string);\n}\n*/\n\nfunction make_operation_exp_ef(exp, machine, labels, operations) {\n const op = lookup_prim(op_exp_op(exp), operations);\n const aprocs = map(e => make_primitive_exp_ef(e, machine, labels),\n operation_exp_operands(exp));\n\n function perform_operation_exp() {\n return op(map(p => p(), aprocs));\n }\n\n return perform_operation_exp;\n}\n\n/* TODO: probably remove these -- suddenly available through new import chains\nfunction is_operation_exp(exp) {\n return is_tagged_list(head(exp), \"op\");\n}\n\nfunction operation_exp_operands(operation_exp) {\n return tail(operation_exp);\n}\n\nfunction op(name) {\n return list(\"op\", name);\n}\n*/\n\nfunction op_exp_op(operation_exp) {\n return head(tail(head(operation_exp)));\n}\n\nfunction lookup_prim(symbol, operations) {\n const val = assoc(symbol, operations);\n\n return is_undefined(val)\n ? error(symbol, \"unknown operation -- assemble\")\n : head(tail(val));\n}\n\n// PAIR OPERATIONS\n\n// head in \"a\", tail in \"b\"\nconst pair_controller = list(\n \"pair\",\n save(\"continue\"),\n assign(\"continue\", label(\"pair_after_gc\")),\n test(list(op(\"===\"), reg(\"free\"), reg(\"SIZE\"))),\n branch(label(\"begin_garbage_collection\")),\n \"pair_after_gc\",\n restore(\"continue\"),\n perform(list(op(\"vector_set\"), reg(\"the_heads\"), reg(\"free\"), reg(\"a\"))),\n perform(list(op(\"vector_set\"), reg(\"the_tails\"), reg(\"free\"), reg(\"b\"))),\n assign(\"res\", reg(\"free\")),\n assign(\"free\", list(op(\"inc_ptr\"), reg(\"free\"))),\n go_to(reg(\"continue\"))\n);\n\nfunction underlying_javascript_closure(fn) {\n return args => apply_in_underlying_javascript(fn, args);\n}\n\nfunction unwrap_args(fn) {\n return args => fn(map(unwrap_ptr, args));\n}\n\nfunction wrap_return_value(fn) {\n return args => wrap_ptr(fn(args));\n}\n\nfunction primitive_function(fn) {\n return wrap_return_value(unwrap_args(underlying_javascript_closure(fn)));\n}\n\n// 5.3 MEMORY MANAGEMENT\n\nfunction vector_ref(vector, idx) {\n return vector[unwrap_ptr(idx)];\n}\n\nfunction vector_set(vector, idx, val) {\n vector[unwrap_ptr(idx)] = val;\n}\n\nfunction make_vector() {\n return [];\n}\n\nfunction inc_ptr(ptr) {\n return make_ptr_ptr(unwrap_ptr(ptr) + 1);\n}\n\nconst vector_ops = list(\n list(\"vector_ref\", underlying_javascript_closure(vector_ref)),\n list(\"vector_set\", underlying_javascript_closure(vector_set)),\n list(\"inc_ptr\", underlying_javascript_closure(inc_ptr))\n);\n\n// MACHINE SETUP\nconst ptr_ops =\n list(\n list(\"make_ptr_ptr\",\n underlying_javascript_closure(make_ptr_ptr)),\n list(\"make_null_ptr\",\n underlying_javascript_closure(make_null_ptr)),\n list(\"make_no_value_yet_ptr\",\n underlying_javascript_closure(make_no_value_yet_ptr)),\n list(\"make_prog_ptr\", underlying_javascript_closure(make_prog_ptr)),\n list(\"make_broken_heart_ptr\",\n underlying_javascript_closure(make_broken_heart_ptr)),\n list(\"is_number_ptr\",\n wrap_return_value(underlying_javascript_closure(is_number_ptr))),\n list(\"is_bool_ptr\",\n wrap_return_value(underlying_javascript_closure(is_bool_ptr))),\n list(\"is_string_ptr\",\n wrap_return_value(underlying_javascript_closure(is_string_ptr))),\n list(\"is_ptr_ptr\",\n wrap_return_value(underlying_javascript_closure(is_ptr_ptr))),\n list(\"is_null_ptr\",\n wrap_return_value(underlying_javascript_closure(is_null_ptr))),\n list(\"is_undefined_ptr\",\n wrap_return_value(underlying_javascript_closure(is_undefined_ptr))),\n list(\"is_prog_ptr\",\n wrap_return_value(underlying_javascript_closure(is_prog_ptr))),\n list(\"is_no_value_yet_ptr\",\n wrap_return_value(underlying_javascript_closure(is_no_value_yet_ptr))),\n list(\"is_broken_heart_ptr\",\n wrap_return_value(underlying_javascript_closure(is_broken_heart_ptr)))\n);\n\nconst primitive_ops = list(\n list(\"display\", primitive_function(display)),\n list(\"error\", primitive_function(error)),\n list(\"+\", primitive_function((x, y) => x + y)),\n list(\"-\", primitive_function((x, y) => x - y)),\n list(\"*\", primitive_function((x, y) => x * y)),\n list(\"/\", primitive_function((x, y) => x / y)),\n list(\"%\", primitive_function((x, y) => x % y)),\n list(\"===\", primitive_function((x, y) => x === y)),\n list(\"!==\", primitive_function((x, y) => x !== y)),\n list(\"<\", primitive_function((x, y) => x < y)),\n list(\"<=\", primitive_function((x, y) => x <= y)),\n list(\">\", primitive_function((x, y) => x > y)),\n list(\">=\", primitive_function((x, y) => x >= y)),\n list(\"!\", primitive_function(x => !x)),\n list(\"||\", primitive_function((x, y) => x || y)),\n list(\"&&\", primitive_function((x, y) => x && y))\n);\n\nconst gc_ops = list(\n list(\"call_proc\", underlying_javascript_closure(proc => proc()))\n);\n\nconst gc_controller = list(\n \"begin_garbage_collection\",\n perform(list(op(\"call_proc\"), reg(\"root_populate_proc\"))),\n assign(\"free\", list(op(\"make_ptr_ptr\"), constant(0))),\n assign(\"scan\", list(op(\"make_ptr_ptr\"), constant(0))),\n assign(\"old\", reg(\"root\")),\n assign(\"relocate_continue\", label(\"reassign_root\")),\n go_to(label(\"relocate_old_result_in_new\")),\n \"reassign_root\",\n assign(\"root\", reg(\"new\")),\n go_to(label(\"gc_loop\")),\n \"gc_loop\",\n test(list(op(\"===\"), reg(\"scan\"), reg(\"free\"))),\n branch(label(\"gc_flip\")),\n assign(\"old\", list(op(\"vector_ref\"), reg(\"new_heads\"), reg(\"scan\"))),\n assign(\"relocate_continue\", label(\"update_head\")),\n go_to(label(\"relocate_old_result_in_new\")),\n \"update_head\",\n perform(list(op(\"vector_set\"), reg(\"new_heads\"), reg(\"scan\"), reg(\"new\"))),\n assign(\"old\", list(op(\"vector_ref\"), reg(\"new_tails\"), reg(\"scan\"))),\n assign(\"relocate_continue\", label(\"update_tail\")),\n go_to(label(\"relocate_old_result_in_new\")),\n \"update_tail\",\n perform(list(op(\"vector_set\"), reg(\"new_tails\"), reg(\"scan\"), reg(\"new\"))),\n assign(\"scan\", list(op(\"inc_ptr\"), reg(\"scan\"))),\n go_to(label(\"gc_loop\")),\n \"relocate_old_result_in_new\",\n test(list(op(\"is_ptr_ptr\"), reg(\"old\"))),\n branch(label(\"gc_pair\")),\n assign(\"new\", reg(\"old\")),\n go_to(reg(\"relocate_continue\")),\n \"gc_pair\",\n assign(\"oldht\", list(op(\"vector_ref\"), reg(\"the_heads\"), reg(\"old\"))),\n test(list(op(\"is_broken_heart_ptr\"), reg(\"oldht\"))),\n branch(label(\"already_moved\")),\n assign(\"new\", reg(\"free\")),\n // new location for pair\n // Update free pointer\n assign(\"free\", list(op(\"inc_ptr\"), reg(\"free\"))),\n // Copy the head and tail to new memory\n perform(list(op(\"vector_set\"),\n reg(\"new_heads\"), reg(\"new\"), reg(\"oldht\"))),\n assign(\"oldht\", list(op(\"vector_ref\"), reg(\"the_tails\"), reg(\"old\"))),\n perform(list(op(\"vector_set\"),\n reg(\"new_tails\"), reg(\"new\"), reg(\"oldht\"))),\n // Construct the broken heart\n assign(\"oldht\", list(op(\"make_broken_heart_ptr\"))),\n perform(list(op(\"vector_set\"),\n reg(\"the_heads\"), reg(\"old\"), reg(\"oldht\"))),\n perform(list(op(\"vector_set\"),\n reg(\"the_tails\"), reg(\"old\"), reg(\"new\"))),\n go_to(reg(\"relocate_continue\")),\n \"already_moved\",\n assign(\"new\", list(op(\"vector_ref\"), reg(\"the_tails\"), reg(\"old\"))),\n go_to(reg(\"relocate_continue\")),\n \"gc_flip\",\n perform(list(op(\"call_proc\"), reg(\"stack_reassign_proc\"))),\n assign(\"temp\", reg(\"the_tails\")),\n assign(\"the_tails\", reg(\"new_tails\")),\n assign(\"new_tails\", reg(\"temp\")),\n assign(\"temp\", reg(\"the_heads\")),\n assign(\"the_heads\", reg(\"new_heads\")),\n assign(\"new_heads\", reg(\"temp\")),\n perform(list(op(\"call_proc\"), reg(\"root_restore_proc\"))),\n go_to(reg(\"continue\"))\n);\n\nconst error_controller = list(\n \"error\",\n perform(list(op(\"error\"), reg(\"res\"), reg(\"err\"))),\n go_to(label(\"end_evaluation\"))\n);\n\nconst begin_controller = list(\n \"fig_5_14\",\n \"pair4\",\n assign(\"a\", constant(4)),\n assign(\"b\", list(op(\"make_null_ptr\"))),\n assign(\"continue\", label(\"garbage1\")),\n go_to(label(\"pair\")),\n /// The following creates a garbage\n /// pair (9999, 9999) which will\n /// not affect live object count at\n /// the end of the program. You can\n /// verify by adding more garbage\n /// or remove this line. Or\n /// uncomment the use of\n /// dump_memory() below, before and\n /// after GC.\n \"garbage1\",\n assign(\"a\", constant(9999)),\n assign(\"b\", constant(9999)),\n assign(\"continue\", label(\"pair2\")),\n go_to(label(\"pair\")),\n \"pair2\",\n assign(\"a\", constant(3)),\n assign(\"b\", reg(\"res\")),\n assign(\"continue\", label(\"garbage2\")),\n go_to(label(\"pair\")),\n /// The following creates a garbage\n /// pair (9999, 9999) which will\n /// not affect live object count at\n /// the end of the program. You can\n /// verify by adding more garbage\n /// or remove this line. Or\n /// uncomment the use of\n /// dump_memory() below, before and\n /// after GC.\n \"garbage2\",\n assign(\"a\", constant(9999)),\n assign(\"b\", constant(9999)),\n assign(\"continue\", label(\"pair7\")),\n go_to(label(\"pair\")),\n \"pair7\",\n assign(\"temp\", reg(\"res\")),\n assign(\"a\", constant(2)),\n assign(\"b\", list(op(\"make_null_ptr\"))),\n assign(\"continue\", label(\"pair5\")),\n go_to(label(\"pair\")),\n \"pair5\",\n assign(\"a\", constant(1)),\n assign(\"b\", reg(\"res\")),\n assign(\"continue\", label(\"pair1\")),\n go_to(label(\"pair\")),\n \"pair1\",\n assign(\"a\", reg(\"res\")),\n assign(\"b\", reg(\"temp\")),\n assign(\"continue\", label(\"done\")),\n go_to(label(\"pair\")),\n \"done\",\n // dump_memory(), // uncomment to get a dump of heads and tails vectors\n assign(\"continue\", label(\"after_gc\")),\n go_to(label(\"begin_garbage_collection\")),\n \"after_gc\",\n // dump_memory(), // uncomment to get a dump of heads and tails vectors\n go_to(label(\"end_evaluation\")));\n\nconst end_controller = list(\n \"end_evaluation\"\n);\n\nconst ops = accumulate(append, null, list(\n vector_ops,\n ptr_ops,\n gc_ops,\n primitive_ops\n));\n\nconst controller = accumulate(append, null, list(\n begin_controller,\n pair_controller,\n gc_controller,\n error_controller,\n end_controller\n));\n\nfunction make_evaluator_machine(size) {\n const evaluator_machine = make_machine(null, ops, controller);\n set_register_contents(evaluator_machine, \"SIZE\", wrap_ptr(size));\n return evaluator_machine;\n}\n\nconst evaluator_machine = make_evaluator_machine(10000);\n\nset_register_contents(evaluator_machine, \"a\", wrap_ptr(206));\nset_register_contents(evaluator_machine, \"b\", wrap_ptr(40));\n\nstart(evaluator_machine);\nget_register_contents(evaluator_machine, \"free\");\n// [ 'ptr', 108 ] The number of live objects in the program at termination\n\n [ 'ptr', 108 ]\n\n\"gc_flip\",\n assign(\"temp\", reg(\"the_tails\")),\n assign(\"the_tails\", reg(\"new_tails\")),\n assign(\"new_tails\", reg(\"temp\")),\n assign(\"temp\", reg(\"the_heads\")),\n assign(\"the_heads\", reg(\"new_heads\")),\n assign(\"new_heads\", reg(\"temp\"))\n```", - "token_count": 2604, + "content": "In fact, if\nthe target is not ,\nthe above is\nexactly the code our compiler will generate.\n\nInstead we simplify the code by\nsetting up so that the\ncalled function\nwill\nreturn\ndirectly to the place specified by the caller s linkage:\n\n```javascript\nset up continue for linkage and push the marker\nassign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))),\ngo_to(reg(\"val\")),\n```\n\nIf the linkage is a label, we set up\nso that the\nfunction\nwill continue at\nthat label.\n\n(That is, the\ngo_to(reg(\"continue\"))\nthe\ncalled function\nends with becomes equivalent to the\ngo_to(label(linkage))\nat\nfun_return\nabove.)\n\n```javascript\nassign(\"continue\", label(linkage)),\nsave(\"continue\"),\npush_marker_to_stack(),\nassign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))),\ngo_to(reg(\"val\")),\n```\n\nIf the linkage is\n\"return\",\nwe don t need to\nassign :\nIt already holds the desired location.\n\n(That is, the\ngo_to(reg(\"continue\"))\nthe\ncalled function\nends with goes directly to the\nplace where the\ngo_to(reg(\"continue\"))\nat\nfun_return\nwould have gone.)\n\n```javascript\nsave(\"continue\"),\npush_marker_to_stack(),\nassign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))),\ngo_to(reg(\"val\")),\n```\n\nWith this implementation of the\n\"return\"\nlinkage,\nthe compiler generates\ntail-recursive code.\n\nA function call in a return statement whose value is the result to be returned\ndoes a direct transfer, without saving\nunnecessary information on the stack.\n\nSuppose instead that we had handled the case of a\nfunction\ncall with a\nlinkage of \"return\" and a target of\nin the same way as for a\nnon- target.\n\nThis would destroy tail\nrecursion.\n\nOur system would still\nreturn\nthe same value for any\nfunction call.\n\nBut each time we called a\nfunction,\nwe would save\nand return after the call to undo the (useless) save.\n\nThese extra\nsaves would accumulate during a nest of\nfunction\ncalls.\n\nThe function compile_@fun_@appl\ngenerates the above\nfunction-application\ncode by considering four cases,\ndepending on whether the target for the call\nis and whether the linkage is\n\"return\".", + "token_count": 294, "has_code": true, "chapter": "Computing with Register Machines", - "section": "Storage Allocation and Garbage Collection", - "subsection": "Maintaining the Illusion of Infinite Memory", + "section": "Compilation", + "subsection": "Compiling Applications and Return Statements", "chunk_index": 6, - "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_6" + "chunk_id": "Computing_with_Register_Machines_Compiling_Applications_and_Return_Statements_6" }, { - "content": "A conventional computer memory can be thought of as an array of\ncubbyholes, each of which can contain a piece of information.\n\nEach\ncubbyhole has a unique name, called its\naddress or\nlocation.\n\nTypical memory systems provide two primitive operations:\none that fetches the data stored in a specified location and one that\nassigns new data to a specified location.\n\nMemory addresses can be\nincremented to support sequential access to some set of the\ncubbyholes.\n\nMore generally, many important data operations require\nthat memory addresses be treated as data, which can be stored in\nmemory locations and manipulated in machine registers.\n\nThe\nrepresentation of list structure is one application of such\naddress arithmetic.\n\nTo model computer memory, we use a new kind of data structure called a\nvector.\n\nAbstractly, a vector is a compound data object whose\nindividual elements can be accessed by means of an integer index in an\namount of time that is independent of the index.\nfunctions\nfor manipulating vectors:\n-\n-\nvector_ref(vector, n)\nn th element of the vector.\n-\n-\nvector_set(vector, n, value)\n$n$ th element of the vector to the\ndesignated value.\n\nFor example, if\nvector_ref(v, 5)\ngets the fifth entry in the vector\nvector_set(v, 5, 7)\nchanges the value of the fifth entry of the vector\nbase address\nthat specifies the beginning location of a vector in memory with an\nindex that specifies the offset of a particular element of the\nvector.\n\nWe can use vectors to implement the basic pair structures required for a\nlist-structured memory.\n\nLet us imagine that computer memory is divided into\ntwo vectors:\nthe_heads\nand\nthe_tails.\n\nWe will represent list structure as follows: A pointer to a pair is an index\ninto the two vectors.", - "token_count": 287, - "has_code": false, + "content": "The function compile_@fun_@appl\ngenerates the above\nfunction-application\ncode by considering four cases,\ndepending on whether the target for the call\nis and whether the linkage is\n\"return\".\n\n```javascript\ncompile_fun_appl\n\nfunction compile_fun_appl(target, linkage) {\n const fun_return = make_label(\"fun_return\");\n return target === \"val\" && linkage !== \"return\"\n ? make_instruction_sequence(list(\"fun\"), all_regs,\n list(assign(\"continue\", label(linkage)),\n save(\"continue\"),\n push_marker_to_stack(),\n assign(\"val\", list(op(\"compiled_function_entry\"),\n reg(\"fun\"))),\n go_to(reg(\"val\"))))\n : target !== \"val\" && linkage !== \"return\"\n ? make_instruction_sequence(list(\"fun\"), all_regs,\n list(assign(\"continue\", label(fun_return)),\n save(\"continue\"),\n push_marker_to_stack(),\n assign(\"val\", list(op(\"compiled_function_entry\"),\n reg(\"fun\"))),\n go_to(reg(\"val\")),\n fun_return,\n assign(target, reg(\"val\")),\n go_to(label(linkage))))\n : target === \"val\" && linkage === \"return\"\n ? make_instruction_sequence(list(\"fun\", \"continue\"),\n all_regs,\n list(save(\"continue\"),\n push_marker_to_stack(),\n assign(\"val\", list(op(\"compiled_function_entry\"),\n reg(\"fun\"))),\n go_to(reg(\"val\"))))\n : // $\\texttt{target !== \"val\" \\&\\& linkage === \"return\"}$\n error(target, \"return linkage, target not val -- compile\");\n}\n```\n\nWe have shown how to generate tail-recursive linkage code for a\nfunction application when the linkage is \"return\" that is, when the application is in a return statement\nand its value is the result to be returned.\n\nSimilarly, as explained in\nsection , the stack-marker mechanism used here (and in the\nexplicit-control evaluator) for the call and return produces\ntail-recursive behavior only in that situation.\n\nThese two aspects of the code generated for function\napplication combine to ensure that when a function ends by returning\nthe value of a function call, no stack accumulates.\n\nThe code for a return statement takes the following form, regardless of the given linkage and target:\n\n```javascript\nrevert_stack_to_marker(),\nrestore(\"continue\"), // saved by $\\texttt{compile\\char`_fun\\char`_appl}$\nevaluate the return expression and store the result in val\ngo_to(reg(\"continue\")) // $\\texttt{\"return\"}$-linkage code\n```\n\nThe instructions to revert the stack using the marker and then restore\ncontinue correspond to the\ninstructions generated by compile_@fun_@appl\nto save continue and mark the stack.\n\nThe final jump to continue is\ngenerated by the use of the \"return\"\nlinkage when compiling the return expression.", + "token_count": 295, + "has_code": true, "chapter": "Computing with Register Machines", - "section": "Storage Allocation and Garbage Collection", - "subsection": "Memory as Vectors", + "section": "Compilation", + "subsection": "Compiling Applications and Return Statements", + "chunk_index": 7, + "chunk_id": "Computing_with_Register_Machines_Compiling_Applications_and_Return_Statements_7" + }, + { + "content": "The final jump to continue is\ngenerated by the use of the \"return\"\nlinkage when compiling the return expression.\n\n```javascript\ncompile_return\n\nfunction compile_return_statement(stmt, target, linkage) {\n return append_instruction_sequences(\n make_instruction_sequence(null, list(\"continue\"),\n list(revert_stack_to_marker(),\n restore(\"continue\"))),\n compile(return_expression(stmt), \"val\", \"return\"));\n}\n```", + "token_count": 37, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Compiling Applications and Return Statements", + "chunk_index": 8, + "chunk_id": "Computing_with_Register_Machines_Compiling_Applications_and_Return_Statements_8" + }, + { + "content": "In this section and the next we implement the code generators to which the function dispatches.\n\nIn general, the output of each code generator will end with\ninstructions generated by the\nfunction\ncompile_linkagethat\nimplement the required linkage.\n\nIf the linkage is\n\"return\"\nthen\nwe must generate the instruction\ngo_to(reg(\"continue\")).\n\nThis needs the register and does not\nmodify any registers.\n\nIf the linkage is\n\"next\",\nthen we needn t include any additional instructions.\n\nOtherwise, the\nlinkage is a label, and we generate a\ngo_to\nto that label, an instruction that does not need or modify\nany registers.\n\n```javascript\ncompile_linkage\n\nfunction compile_linkage(linkage) {\n return linkage === \"return\"\n ? make_instruction_sequence(list(\"continue\"), null,\n list(go_to(reg(\"continue\"))))\n : linkage === \"next\"\n ? make_instruction_sequence(null, null, null)\n : make_instruction_sequence(null, null,\n list(go_to(label(linkage))));\n}\n```\n\nThe linkage code is appended to an instruction sequence by the register, since a \"return\" linkage will require the register: If the given instruction sequence\n\nmodifies and the linkage code needs it, will be saved and restored.\n\n```javascript\nend_with_linkage\n\nfunction end_with_linkage(linkage, instruction_sequence) {\n return preserving(list(\"continue\"),\n instruction_sequence,\n compile_linkage(linkage));\n}\n```\n\nThe code generators for literal expressions and names construct instruction sequences that assign the required value to the target register and then proceed as specified\n\nby the linkage descriptor.\n\nThe literal value is extracted at compile time from the component being\ncompiled and put into the constant part of the\nassign instruction.\n\nFor a name, an\ninstruction is generated to use the\nlookup_symbol_value operation when\nthe compiled program is run, to look up the value associated with a\nsymbol in the current environment.\n\nLike a literal value, the symbol is\nextracted at compile time from the component being compiled.\n\nThus\nsymbol_of_name(component) is\nexecuted only once, when the program is being compiled, and the symbol\nappears as a constant in the\nassign instruction.", + "token_count": 294, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Compiling Components", "chunk_index": 1, - "chunk_id": "Computing_with_Register_Machines_Memory_as_Vectors_1" + "chunk_id": "Computing_with_Register_Machines_Compiling_Components_1" }, { - "content": "We will represent list structure as follows: A pointer to a pair is an index\ninto the two vectors.\n\nThe\nhead\nof the pair is the entry in\nthe_heads\nwith the designated index, and the\ntail\nof the pair is the entry in\nthe_tails\nwith the designated index.\n\nWe also need a representation for objects other\nthan pairs (such as numbers and\nstrings)\nand a way to distinguish one kind of data from another.\n\nThere are many\nmethods of accomplishing this, but they all reduce to using\ntyped pointers , that is, to extending the notion of\npointer to include information on data type. pair\ndata type and an index into the memory vectors) from pointers to other\nkinds of data (which consist of some other data type and whatever is\nbeing used to represent data of that type).\n\nTwo data objects are\n\n```javascript\n(===)\n```\n\nif their pointers are identical.\n\nFigure\nillustrates the use of this method to represent\nlist(list(1, 2), 3, 4),\nwhose box-and-pointer diagram is also shown.\n\nWe use letter prefixes to\ndenote the data-type information.\n\nThus, a pointer to the pair with\nindex 5 is denoted 4 is denoted\nhead\nand\ntail\nof the pair are stored.\n\nThe blank locations in\nthe_heads\nand\nthe_tails\nmay contain parts of other list structures (not of interest here).\n\nA pointer to a number, such as bignum data type, for which the pointer designates a list in which the parts of the number are\n\nstored.\n\nA string\ns printed\nrepresentation.\n\nThe parser constructs such a sequence\nwhen it encounters a string literal, and the\nstring-concatenation operator + and\nstring-producing\nprimitive functions such as\nstringify\nconstruct such a sequence.", - "token_count": 278, + "content": "Thus\nsymbol_of_name(component) is\nexecuted only once, when the program is being compiled, and the symbol\nappears as a constant in the\nassign instruction.\n\nThese assignment instructions modify the target register, and the one that looks up a symbol needs the register.\n\nAssignments and\ndeclarations\nare handled much as they are in the\ninterpreter.\n\nThe function compile_assignment_declaration recursively generates code that computes the value to be associated with the symbol and appends to it a two-instruction sequence that updates the value associated with the symbol in the environment and assigns the value of the whole component (the assigned value for an assignment or undefined for a declaration) to the target register.\n\nThe recursive compilation has target val and linkage \"next\" so that the code will put its result into val and continue with the code that is appended after it.\n\nThe appending is done preserving env, since the environment is needed for updating the symbolvalue association and the code for computing the value could be the compilation of a complex expression that might modify the registers in arbitrary ways.\n\n```javascript\ncompile_assignment\n\nfunction compile_assignment(component, target, linkage) {\n return compile_assignment_declaration(\n assignment_symbol(component),\n assignment_value_expression(component),\n reg(\"val\"),\n target, linkage);\n}\n\nfunction compile_declaration(component, target, linkage) {\n return compile_assignment_declaration(\n declaration_symbol(component),\n declaration_value_expression(component),\n constant(undefined),\n target, linkage);\n}\nfunction compile_assignment_declaration(\n symbol, value_expression, final_value,\n target, linkage) {\n const get_value_code = compile(value_expression, \"val\", \"next\");\n return end_with_linkage(linkage,\n preserving(list(\"env\"),\n get_value_code,\n make_instruction_sequence(list(\"env\", \"val\"),\n list(target),\n list(perform(list(op(\"assign_symbol_value\"),\n constant(symbol),\n reg(\"val\"),\n reg(\"env\"))),\n assign(target, final_value)))));\n}\n```\n\nThe appended two-instruction sequence requires\nand\nand modifies the target.\n\nNote that although we preserve\nfor this sequence, we do not preserve\n, because the\nget_value_code\nis designed to explicitly place its result in\nfor use by this sequence.", + "token_count": 274, "has_code": true, "chapter": "Computing with Register Machines", - "section": "Storage Allocation and Garbage Collection", - "subsection": "Memory as Vectors", + "section": "Compilation", + "subsection": "Compiling Components", "chunk_index": 2, - "chunk_id": "Computing_with_Register_Machines_Memory_as_Vectors_2" + "chunk_id": "Computing_with_Register_Machines_Compiling_Components_2" }, { - "content": "The parser constructs such a sequence\nwhen it encounters a string literal, and the\nstring-concatenation operator + and\nstring-producing\nprimitive functions such as\nstringify\nconstruct such a sequence.\n\nSince we want two instances of a string to\nbe recognized as the same string by\n=== and we want\n===\nto\nbe a simple test for equality of pointers, we must ensure that if the\nsystem sees the same string twice, it will use the same pointer (to\nthe same sequence of characters) to represent both occurrences.\n\nTo\naccomplish this, the system maintains a table, called the\nstring pool ,\nof all the strings it has ever encountered.\n\nWhen the system\nis about to construct a string, it checks the string pool to see if it has ever\nbefore seen the same string.\n\nIf it has not, it\nconstructs a new string (a typed pointer to a new\ncharacter sequence) and enters this pointer in the string pool.\n\nIf the\nsystem has seen the string before, it returns the string pointer\nstored in the string pool.\n\nThis process of replacing strings by unique\npointers is called\nstring interning.\n\nGiven the above representation , we can replace each\nprimitive list operation of a register machine with one or\nmore primitive vector operations.\n\nWe will use two registers,\nthe_heads\nand\nthe_tails,\nto identify the memory vectors, and will\nassume that\nvector_ref\nand\nvector_set\nare available as primitive operations.\n\nWe also assume that numeric\noperations on pointers (such as incrementing a pointer, using a pair pointer\nto index a vector, or adding two numbers) use only the index portion of\nthe typed pointer.\n\nFor example, we can make a register machine support the instructions\n\n```javascript\nassign(reg$_1$, list(op(\"head\"), reg(reg$_2$)))\n\nassign(reg$_1$, list(op(\"tail\"), reg(reg$_2$)))\n```\n\nif we implement these, respectively, as\n\n```javascript\nassign(reg$_1$, list(op(\"vector_ref\"), reg(\"the_heads\"), reg(reg$_2$)))\n\nassign(reg$_1$, list(op(\"vector_ref\"), reg(\"the_tails\"), reg(reg$_2$)))\n```", + "content": "Note that although we preserve\nfor this sequence, we do not preserve\n, because the\nget_value_code\nis designed to explicitly place its result in\nfor use by this sequence.\n\nThe code for a conditional compiled with a given target and linkage has the form\n\n```javascript\ncompilation of predicate, target val, linkage \"next\"\n test(list(op(\"is_falsy\"), reg(\"val\"))),\n branch(label(\"false_branch\")),\n\"true_branch\",\n compilation of consequent with given target and given linkage or after_cond\n\"false_branch\",\n compilation of alternative with given target and linkage\n\"after_cond\"\n```\n\nTo generate this code, we compile the predicate, consequent,\nand alternative, and combine the resulting code with instructions\nto test the predicate result and with newly generated labels to mark the\ntrue and false branches and the end of the\nconditional.\n\nIn this arrangement of code, we must branch around the true branch\nif the test is false.\n\nThe only slight complication is in how the\nlinkage for the true branch should be handled.\n\nIf the linkage for the\nconditional is\n\"return\"\nor a label, then the\ntrue and false branches will both use this same linkage.\n\nIf the linkage is\n\"next\",\nthe true branch ends with a jump around\nthe code for the false branch to the label at the end of the conditional.\n\n```javascript\ncompile_conditional\n\nfunction compile_conditional(component, target, linkage) {\n const t_branch = make_label(\"true_branch\");\n const f_branch = make_label(\"false_branch\");\n const after_cond = make_label(\"after_cond\");\n const consequent_linkage =\n linkage === \"next\" ? after_cond : linkage;\n const p_code = compile(conditional_predicate(component),\n \"val\", \"next\");\n const c_code = compile(conditional_consequent(component),\n target, consequent_linkage);\n const a_code = compile(conditional_alternative(component),\n target, linkage);\n return preserving(list(\"env\", \"continue\"),\n p_code,\n append_instruction_sequences(\n make_instruction_sequence(list(\"val\"), null,\n list(test(list(op(\"is_falsy\"), reg(\"val\"))),\n branch(label(f_branch)))),\n append_instruction_sequences(\n parallel_instruction_sequences(\n append_instruction_sequences(t_branch, c_code),\n append_instruction_sequences(f_branch, a_code)),\n after_cond)));\n}\n```\n\n\\newpage\\noindent The register\nis preserved around the predicate code\nbecause it could be needed by the true and false\nbranches, and is preserved because it could\nbe needed by the linkage code in those branches.", "token_count": 304, "has_code": true, "chapter": "Computing with Register Machines", - "section": "Storage Allocation and Garbage Collection", - "subsection": "Memory as Vectors", + "section": "Compilation", + "subsection": "Compiling Components", "chunk_index": 3, - "chunk_id": "Computing_with_Register_Machines_Memory_as_Vectors_3" + "chunk_id": "Computing_with_Register_Machines_Compiling_Components_3" }, { - "content": "if we implement these, respectively, as\n\nThe instructions\n\n```javascript\nperform(list(op(\"set_head\"), reg(reg$_1$), reg(reg$_2$)))\n\nperform(list(op(\"set_tail\"), reg(reg$_1$), reg(reg$_2$)))\n```\n\nare implemented as\n\n```javascript\nperform(list(op(\"vector_set\"), reg(\"the_heads\"), reg(reg$_1$), reg(reg$_2$)))\n\nperform(list(op(\"vector_set\"), reg(\"the_tails\"), reg(reg$_1$), reg(reg$_2$)))\n```\n\n```javascript\nThe operation\n\tpair\n```\n\nis performed by allocating an unused index and storing the arguments to\npair\nin\nthe_heads\nand\nthe_tails\nat that indexed vector position.\n\nWe presume that there is a special\nregister,\n\n```javascript\nassign(reg$_1$, list(op(\"pair\"), reg(reg$_2$), reg(reg$_3$)))\n```\n\nis implemented as the following sequence of vector operations:\n\n```javascript\nperform(list(op(\"vector_set\"),\n reg(\"the_heads\"), reg(\"free\"), reg(reg$_2$))),\nperform(list(op(\"vector_set\"),\n reg(\"the_tails\"), reg(\"free\"), reg(reg$_3$))),\nassign(reg$_1$, reg(\"free\")),\nassign(\"free\", list(op(\"+\"), reg(\"free\"), constant(1)))\n```\n\nThe\n\n```javascript\n===\n```\n\noperation\n\n```javascript\nlist(op(\"===\"), reg(reg$_1$), reg(reg$_2$))\n```\n\nsimply tests the equality of all fields in the registers, and predicates such as is_pair, is_null, is_string, and is_number need only check the type field.\n\nAlthough our register machines use stacks, we need do nothing special\nhere, since stacks can be modeled in terms of lists.\n\nThe stack can be\na list of the saved values, pointed to by a special register\nthe_stack.\n\nThus,\nsave(reg)\ncan be implemented as\n\n```javascript\nassign(\"the_stack\", list(op(\"pair\"), reg(reg), reg(\"the_stack\")))\n```\n\nSimilarly, restore(reg) can be implemented as\n\n```javascript\nassign(reg, list(op(\"head\"), reg(\"the_stack\")))\nassign(\"the_stack\", list(op(\"tail\"), reg(\"the_stack\")))\n```\n\nand perform(list(op(\"initialize_stack\"))) can be implemented as\n\n```javascript\nassign(\"the_stack\", constant(null))\n```\n\nThese operations can be further expanded in terms of the vector\noperations given above.\n\nIn conventional computer architectures,\nhowever, it is usually advantageous to allocate the stack as a\nseparate vector.\n\nThen pushing and popping the stack can be\naccomplished by incrementing or decrementing an index into that\nvector.", - "token_count": 253, + "content": "\\newpage\\noindent The register\nis preserved around the predicate code\nbecause it could be needed by the true and false\nbranches, and is preserved because it could\nbe needed by the linkage code in those branches.\n\nThe compilation of\nstatement sequences parallels their evaluation in the explicit-control evaluator with one exception: If a return statement appears anywhere in a sequence, we treat it as if it were the last statement.\n\nEach\nstatement\nof the sequence is compiled the last\nstatement (or a return statement)\nwith\nthe linkage specified for the sequence, and the other\nstatements\nwith\nlinkage\n\"next\"\n(to execute the rest of the\nsequence).\n\nThe instruction sequences for the individual\nstatements\nare\nappended to form a single instruction sequence, such that\n(needed for the rest of the\nsequence)\nand (possibly needed for the linkage at the end of the sequence) are preserved.\n\n```javascript\ncompile_sequence\n\nfunction compile_sequence(seq, target, linkage) {\n return is_empty_sequence(seq)\n ? compile_literal(make_literal(undefined), target, linkage)\n : is_last_statement(seq) ||\n is_return_statement(first_statement(seq))\n ? compile(first_statement(seq), target, linkage)\n : preserving(list(\"env\", \"continue\"),\n compile(first_statement(seq), target, \"next\"),\n compile_sequence(rest_statements(seq),\n target, linkage));\n}\n```\n\nTreating a return statement as if it were the last statement in a sequence avoids compiling any dead code after the return statement that can never be executed.\n\nRemoving the is_return_statement check does not change the behavior of the object program; however, there are many reasons not to compile dead code, which are beyond the scope of this book (security, compilation time, size of the object code, etc.), and many compilers give warnings for dead code.\n\nA\nblock is compiled by prepending an assign instruction to the compiled\nbody of the block.\n\nThe assignment extends the current environment by a frame\nthat binds the names declared in the block to the value\n\"*unassigned*\".\n\nThis operation\nboth needs and modifies the env register.", + "token_count": 296, "has_code": true, "chapter": "Computing with Register Machines", - "section": "Storage Allocation and Garbage Collection", - "subsection": "Memory as Vectors", + "section": "Compilation", + "subsection": "Compiling Components", "chunk_index": 4, - "chunk_id": "Computing_with_Register_Machines_Memory_as_Vectors_4" + "chunk_id": "Computing_with_Register_Machines_Compiling_Components_4" + }, + { + "content": "This operation\nboth needs and modifies the env register.\n\nLambda expressions\nconstruct\nfunctions.\n\nThe object code for a lambda expression must have the form\n\n```javascript\nconstruct function object and assign it to target register\nlinkage\n```\n\nWhen we compile the lambda expression, we also generate the code for the\nfunction\nbody.\n\nAlthough the body won t be executed at the time of\nfunction\nconstruction, it is convenient to insert it into the object code right after\nthe code for the\nlambda expression.\n\nIf the linkage for the lambda expression is a label or\n\"return\",\nthis is fine.\n\nBut if the linkage is\n\"next\",\nwe will need to skip around the code for\nthe\nfunction\nbody by using a linkage that jumps to a label that is inserted after the\nbody.\n\nThe object code thus has the form\n\n```javascript\nconstruct function object and assign it to target register\ncode for given linkage or go_to(label(\"after_lambda\"))\ncompilation of function body\n\"after_lambda\"\n```\n\nThe function compile_lambda_expression\ngenerates the code for constructing the\nfunction\nobject followed by the code for the\nfunction\nbody.\n\nThe\nfunction\nobject will be constructed at run time by combining the current environment\n(the environment at the point of declaration ) with the entry point to the\ncompiled\nfunction\nbody (a newly generated label).\n\n```javascript\ncompile_lambda\n\nfunction compile_lambda_expression(exp, target, linkage) {\n const fun_entry = make_label(\"entry\");\n const after_lambda = make_label(\"after_lambda\");\n const lambda_linkage =\n linkage === \"next\" ? after_lambda : linkage;\n return append_instruction_sequences(\n tack_on_instruction_sequence(\n end_with_linkage(lambda_linkage,\n make_instruction_sequence(list(\"env\"),\n list(target),\n list(assign(target,\n list(op(\"make_compiled_function\"),\n label(fun_entry),\n reg(\"env\")))))),\n compile_lambda_body(exp, fun_entry)),\n after_lambda);\n}\n```\n\nThe function compile_lambda_expression uses the special combiner tack_@on_@instruction_@sequence ( from section ) rather than append_instruction_@sequences to append the function body to the lambda expression code,\n\nbecause the body is not part of the sequence of instructions that will be executed when the combined sequence is entered; rather, it is in", + "token_count": 303, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Compiling Components", + "chunk_index": 5, + "chunk_id": "Computing_with_Register_Machines_Compiling_Components_5" + }, + { + "content": "because the body is not part of the sequence of instructions that will be executed when the combined sequence is entered; rather, it is in\n\nThe function compile_lambda_body\nconstructs the code for the body of the\nfunction.\n\nThis code begins with a label for the entry point.\n\nNext come instructions\nthat will cause the runtime evaluation environment to switch to the correct\nenvironment for evaluating the\nfunction\nbody namely, the\nenvironment of the\nfunction,\nextended to include the bindings of the\nparameters to the arguments\nwith which the\nfunction\nis called.\n\nAfter this comes the code for the\nfunction body, augmented to ensure that it ends with a return statement.\n\nThe augmented body is compiled with target val so that its return value will be placed in val.\n\nThe linkage descriptor passed to this compilation is irrelevant, as it will be ignored.\n\nSince a linkage argument is required, we arbitrarily pick \"next\".\n\n```javascript\ncompile_lambda_body\n\t append_return_undefined\n\nfunction compile_lambda_body(exp, fun_entry) {\n const params = lambda_parameter_symbols(exp);\n return append_instruction_sequences(\n make_instruction_sequence(list(\"env\", \"fun\", \"argl\"),\n list(\"env\"),\n list(fun_entry,\n assign(\"env\",\n list(op(\"compiled_function_env\"),\n reg(\"fun\"))),\n assign(\"env\",\n list(op(\"extend_environment\"),\n constant(params),\n reg(\"argl\"),\n reg(\"env\"))))),\n compile(append_return_undefined(lambda_body(exp)),\n \"val\", \"next\"));\n}\n```\n\nTo ensure that all functions end by executing a return statement,\ncompile_@lambda_@body\nappends to the lambda body a return statement whose return expression is the literal\nundefined.\n\nTo do so, it uses the function append_@return_@undefined ,\nwhich constructs the parser s tagged-list representation (from section ) of a\nsequence consisting of the body and a return undefined; statement.\n\n```javascript\nappend_return_undefined\n\nfunction append_return_undefined(body) {\n return list(\"sequence\", list(body,\n list(\"return_statement\",\n list(\"literal\", undefined))));\n}\n```\n\nThis simple transformation of lambda bodies is a third way to ensure that a\nfunction that does not return explicitly has the return value\nundefined.\n\nIn the metacircular evaluator, we used a return-value object, which also played a\nrole in stopping a sequence evaluation.", + "token_count": 298, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Compiling Components", + "chunk_index": 6, + "chunk_id": "Computing_with_Register_Machines_Compiling_Components_6" + }, + { + "content": "In the metacircular evaluator, we used a return-value object, which also played a\nrole in stopping a sequence evaluation.\n\nSee exercise for a more elegant way\nto handle insertion of return statements.\n\nHint: The answer depends on how we define dead code.\n\nOne possible (and useful)\ndefinition is code following a return statement in a sequence but\nwhat about code in the consequent\nbranch of if (false) $\\ldots$ or\ncode following a\ncall to run_forever() in exercise ?", + "token_count": 78, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Compiling Components", + "chunk_index": 7, + "chunk_id": "Computing_with_Register_Machines_Compiling_Components_7" }, { - "content": "In section we saw how to\ntransform simple\nJavaScript\nprograms into descriptions of register\nmachines.\n\nWe will now perform this transformation on a more complex\nprogram, the metacircular evaluator of\nsections ,\nwhich shows how the behavior of a\nJavaScript\ninterpreter can be described in terms of the\nfunctions\nand explicit-control\nevaluator that we develop in this section shows how the underlying\nfunction-calling\nand argument-passing mechanisms used in the\nevaluation process can be described in terms of operations on\nregisters and stacks.\n\nIn addition, the explicit-control evaluator can\nserve as an implementation of a\nJavaScript\ninterpreter, written in a language that is very similar to the native machine\nlanguage of conventional computers.\n\nThe evaluator can be executed by the\nregister-machine simulator of section.\n\nAlternatively, it can be used as a starting point for building a\nmachine-language implementation of a\nJavaScript\nevaluator, or even a\nJavaScript programs.\n\nFigure shows such a hardware\nimplementation: a silicon chip that acts as an evaluator for\n, the language used in place of JavaScript in the original edition of this book.\n\nThe chip designers started with the data-path and controller specifications\nfor a register machine similar to the evaluator described in this section\nand used design automation programs to construct the\nintegrated-circuit layout.\n\nIn designing the explicit-control evaluator, we must specify the\noperations to be used in our register machine.\n\nWe described the\nmetacircular evaluator in terms of abstract syntax, using\nfunctions\nsuch as\nis_literal\nand\nmake_function.\n\nIn implementing the\nregister machine, we could expand these\nfunctions\ninto sequences of\nelementary list-structure memory operations, and implement these\noperations on our register machine.\n\nHowever, this would make our\nevaluator very long, obscuring the basic structure with\ndetails.", + "content": "We have not yet explained how to load compiled code into the evaluator\nmachine or how to run it.\n\nWe will assume that the explicit-control-evaluator\nmachine has been defined as in\nsection , with the additional\noperations specified in footnote (section ).\n\nWe will implement a\nfunction\ncompile_and_go\nthat compiles a\nJavaScript program,\nloads the resulting object code into the evaluator machine,\nand causes the machine to run the code in the\nevaluator global environment, print the result, and\nenter the evaluator s driver loop.\n\nWe will also modify the evaluator\nso that interpreted\ncomponents\ncan call compiled\nfunctions\nas well as interpreted ones.\n\nWe can then put a compiled\nfunction\ninto the machine and use the\nevaluator to call it:\n\n```javascript\ncompile_and_go_example\n compile_and_go\n\ncompile_and_go(parse(`\nfunction factorial(n) {\n return n === 1\n ? 1\n : factorial(n - 1) * n;\n}\n `));\n\nEC-evaluate value:\nundefined\n```\n\n```javascript\nEC-evaluate input:\n\nfactorial(5);\n\nEC-evaluate value:\n120\n```\n\nTo allow the evaluator to handle compiled functions (for example, to evaluate the call to above), we need to change the code at apply_dispatch (section\n\n) so that it recognizes compiled functions (as distinct from compound or primitive functions) and transfers control directly to the entry point of the compiled\n\ncode:\n\n```javascript\n\"apply_dispatch\",\n test(list(op(\"is_primitive_function\"), reg(\"fun\"))),\n branch(label(\"primitive_apply\")),\n test(list(op(\"is_compound_function\"), reg(\"fun\"))),\n branch(label(\"compound_apply\")),\n test(list(op(\"is_compiled_function\"), reg(\"fun\"))),\n branch(label(\"compiled_apply\")),\n go_to(label(\"unknown_function_type\")),\n\n\"compiled_apply\",\n push_marker_to_stack(),\n assign(\"val\", list(op(\"compiled_function_entry\"), reg(\"fun\"))),\n go_to(reg(\"val\")),\n```\n\nAt compiled_apply, as at compound_apply, we push a marker to the stack so that a return statement in the compiled function can revert the stack to this state.\n\nNote that there is no save of continue at compiled_apply before the marking of the stack, because the evaluator was arranged so that at apply_dispatch, the continuation would be at the top of the stack.", + "token_count": 287, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Interfacing Compiled Code to the Evaluator", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Interfacing_Compiled_Code_to_the_Evaluator_1" + }, + { + "content": "Note that there is no save of continue at compiled_apply before the marking of the stack, because the evaluator was arranged so that at apply_dispatch, the continuation would be at the top of the stack.\n\nwhich causes the machine to go to a new entry point if the register is set.\n\n```javascript\n$\\texttt{ }\\texttt{ }$branch(label(\"external_entry\")), // branches if flag is set\n\"read_evaluate_print_loop\",\n perform(list(op(\"initialize_stack\"))),\n $\\ldots$\n```\n\nThe code at external_entry\nassumes that the machine is started with\ncontaining the location of an instruction sequence that puts a result into\nand ends with\ngo_to(reg(\"continue\")).\n\nStarting at this entry point jumps to the location designated\nby , but first assigns\nso that execution will return to\nprint_result,\nwhich prints the value in and then goes to\nthe beginning of the evaluator s\nread-evaluate-print\nloop.\n\n```javascript\n\"external_entry\",\n perform(list(op(\"initialize_stack\"))),\n assign(\"env\", list(op(\"get_current_environment\"))),\n assign(\"continue\", label(\"print_result\")),\n go_to(reg(\"val\")),\n```\n\nNow we can use the following\nfunction\nto compile a\nfunction declaration,\nexecute the compiled code, and run the\nread-evaluate-print\nloop so\nwe can try the\nfunction.\n\nBecause we want the compiled code to\nproceed\nto the location in\nwith its result in\n, we compile the\nprogram\nwith a target of and a\nlinkage of\n\"return\".\n\nIn order to transform the\nobject code produced by the compiler into executable instructions\nfor the evaluator register machine, we use the\nfunction\nfrom the\nregister-machine simulator\n(section ).\n\nFor the interpreted program to refer to the names that are declared at top level in the compiled program, we scan out the top-level names and extend the global environment by binding these names to \"*unassigned*\", knowing that the compiled code will assign them the correct values.\n\nWe then initialize\nthe register to point to the list\nof instructions, set the\nso that the evaluator will go to\nexternal_entry,\nand start the evaluator.", + "token_count": 299, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Interfacing Compiled Code to the Evaluator", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Interfacing_Compiled_Code_to_the_Evaluator_2" + }, + { + "content": "We then initialize\nthe register to point to the list\nof instructions, set the\nso that the evaluator will go to\nexternal_entry,\nand start the evaluator.", + "token_count": 26, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Interfacing Compiled Code to the Evaluator", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_Interfacing_Compiled_Code_to_the_Evaluator_3" + }, + { + "content": "We then initialize\nthe register to point to the list\nof instructions, set the\nso that the evaluator will go to\nexternal_entry,\nand start the evaluator.\n\n```javascript\ncompile_and_go\n compile_and_go_example\n headline_4_1_1\n scan_out_declarations\n list_of_unassigned\n functions_4_1_2\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n make_machine\n start\n compile\n make_instruction_sequence\n compile_linkage\n end_with_linkage\n compile_literal\n compile_assignment\n make_label\n compile_conditional\n compile_sequence\n make_compiled_function\n compile_lambda\n compile_lambda_body\n compile_return\n compile_block\n compile_application\n construct_arglist\n compile_function_call\n all_regs\n compile_fun_appl\n registers_needed\n needs_register\n append_instruction_sequences\n list_union\n preserving\n tack_on_instruction_sequence\n parallel_instruction_sequences\n user_print_2\n eceval_2\n display_instructions\n\nfunction compile_and_go(program) {\n const instrs = assemble(instructions(compile(program,\n \"val\", \"return\")),\n eceval);\n const toplevel_names = scan_out_declarations(program);\n const unassigneds = list_of_unassigned(toplevel_names);\n set_current_environment(extend_environment(\n toplevel_names,\n unassigneds,\n the_global_environment));\n set_register_contents(eceval, \"val\", instrs);\n set_register_contents(eceval, \"flag\", true);\n return start(eceval);\n}\n```\n\nIf we have set up stack monitoring, as at the end of section , we can examine the stack usage of compiled code:\n\n```javascript\ncompile_and_go_example_2\n compile_and_go\n\ncompile_and_go(parse(`\nfunction factorial(n) {\n return n === 1\n ? 1\n : factorial(n - 1) * n;\n}\n `));\n\ntotal pushes = 0\nmaximum depth = 0\nEC-evaluate value:\nundefined\n```\n\n```javascript\nEC-evaluate input:\n\nfactorial(5);\n\ntotal pushes = 36\nmaximum depth = 14\nEC-evaluate value:\n120\n```\n\nCompare\nthis example with the evaluation of\nfactorial(5)\nusing the interpreted version of the same\nfunction,\nshown at the end of section.\n\nThe interpreted version required 151 pushes and a maximum stack depth of 28.\n\nThis illustrates the optimization that results from our compilation strategy.\n\nWith the programs in this section, we can now experiment with the\nalternative execution strategies of interpretation and\ncompilation.\n\nAn interpreter raises the machine to the level of the user program; a\ncompiler lowers the user program to the level of the machine language.\n\nWe can regard the\nJavaScript\nlanguage (or any programming language) as a\ncoherent family of abstractions erected on the machine language.", "token_count": 281, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Interfacing Compiled Code to the Evaluator", + "chunk_index": 4, + "chunk_id": "Computing_with_Register_Machines_Interfacing_Compiled_Code_to_the_Evaluator_4" + }, + { + "content": "We can regard the\nJavaScript\nlanguage (or any programming language) as a\ncoherent family of abstractions erected on the machine language.\n\nCompiled code can execute faster, because the steps of program execution\nare organized in terms of the machine language, and the compiler is free\nto make optimizations that cut across the higher-level\nabstractions.\n\nThe alternatives of interpretation and compilation also lead to\ndifferent strategies for\nporting languages to new computers.\n\nSuppose\nthat we wish to implement\nJavaScript\nfor a new machine.\n\nOne strategy is\nto begin with the explicit-control evaluator of\nsection\nand translate its instructions to instructions for the\nnew machine.\n\nA different strategy is to begin with the compiler and\nchange the code generators so that they generate code for the new\nmachine.\n\nThe second strategy allows us to run any\nJavaScript\nprogram on the new machine by first compiling it with the compiler running\non our\noriginal JavaScript system, and linking it with a compiled version of the runtime\nlibrary.\n\nBetter yet, we can compile the compiler itself, and run\nthis on the new machine to compile other\nJavaScript programs.\n\nOr we can compile one of the interpreters of\nsection to produce an interpreter that\nruns on the new machine.\n\n\\quad\nTake the ratio of the number of pushes in the compiled version to the\nnumber of pushes in the interpreted version, and do the same for the\nmaximum stack depth.\n\nSince the number of operations and the stack\ndepth used to compute $n!$ are linear in\n$n$ , these ratios should\napproach constants as $n$ becomes large.\n\nWhat are these constants?\n\nSimilarly, find the ratios of the stack usage\nin the special-purpose machine to the usage in the interpreted version.", + "token_count": 285, "has_code": false, "chapter": "Computing with Register Machines", - "section": "The Explicit-Control Evaluator", - "subsection": null, + "section": "Compilation", + "subsection": "Interfacing Compiled Code to the Evaluator", + "chunk_index": 5, + "chunk_id": "Computing_with_Register_Machines_Interfacing_Compiled_Code_to_the_Evaluator_5" + }, + { + "content": "Similarly, find the ratios of the stack usage\nin the special-purpose machine to the usage in the interpreted version.\n\nYou should find that the\nspecial-purpose machine is much more efficient than the compiled code, since\nthe hand-tailored controller code should be much better than what is\nproduced by our rudimentary general-purpose compiler.", + "token_count": 52, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Interfacing Compiled Code to the Evaluator", + "chunk_index": 6, + "chunk_id": "Computing_with_Register_Machines_Interfacing_Compiled_Code_to_the_Evaluator_6" + }, + { + "content": "One of the most common optimizations performed by compilers is the\noptimization of\nname\nlookup.\n\nOur compiler, as we have implemented it so far, generates code that\nuses the\nlookup_symbol_value\noperation of the evaluator machine.\n\nThis searches for a\nname\nby comparing\nit\nwith each\nname\nthat is currently bound, working frame\nby frame outward through the runtime environment.\n\nThis search can be\nexpensive if the frames are deeply nested or if there are many\nnames.\n\nFor example, consider the problem of looking up the value\nof while evaluating the expression\nx * y * z\nin an application of the\nfunction of five arguments\nthat is returned by\n\n```javascript\n((x, y) =>\n (a, b, c, d, e) =>\n ((y, z) => x * y * z)(a * b * x, c + d + x))(3, 4)\n```\n\nEach time\nlookup_symbol_value\nsearches for , it must determine that\nthe symbol\n\"x\" is not equal to \"y\" or \"z\" (in the first frame), nor to \"a\", \"b\", \"c\", \"d\", or \"e\" (in the second frame).\n\nBecause our language is\nlexically scoped, the runtime environment for any\ncomponent\nwill have a\nstructure that parallels the lexical structure of the program in which\nthe\ncomponent\nappears.\n\nThus, the compiler can know, when it analyzes the\nabove expression,\nthat each time the\nfunction\nis applied the\nbinding for x\nin\nx * y * z\nwill be found two frames out from the\ncurrent frame and will be the first\nbinding\nin that frame.", + "token_count": 250, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Lexical Addressing", "chunk_index": 1, - "chunk_id": "Computing_with_Register_Machines_The_Explicit-Control_Evaluator_1" + "chunk_id": "Computing_with_Register_Machines_Lexical_Addressing_1" }, { - "content": "However, this would make our\nevaluator very long, obscuring the basic structure with\ndetails.\n\nTo clarify the presentation, we will include as primitive\noperations of the register machine the syntax\nfunctions\ngiven in\nsection and the\nfunctions\nfor representing environments and other runtime data given in\nsections\nand.\n\nIn order to completely specify an evaluator that could be programmed\nin a low-level machine language or implemented in hardware, we would\nreplace these operations by more elementary operations, using the\nlist-structure implementation we described in\nsection.\n\nOur\nJavaScript\nevaluator register machine includes a stack and seven\nregisters:\nThe comp register\nis used to hold the\ncomponent\nto be evaluated, and component\nin the designated environment.\n\nThe.\n\n(The evaluator needs to call itself recursively, since\nevaluating\n\n```javascript\na\n component\n```\n\nrequires evaluating its subcomponents. ) The registers\n\nWe will not provide a data-path diagram to show how the registers and\noperations of the evaluator are connected, nor will we give the\ncomplete list of machine operations.\n\nThese are implicit in the\nevaluator s controller, which will be presented in detail.", - "token_count": 178, + "content": "Thus, the compiler can know, when it analyzes the\nabove expression,\nthat each time the\nfunction\nis applied the\nbinding for x\nin\nx * y * z\nwill be found two frames out from the\ncurrent frame and will be the first\nbinding\nin that frame.\n\nThe operation lexical_address_lookup\nwill produce the value of the\nname\nstored at that lexical address\nrelative to the current environment.\n\nIf we add the\nlexical_address_lookup\noperation to our machine, we can make the compiler generate code that\nreferences\nnames\nusing this operation, rather than\nlookup_symbol_value.\n\nSimilarly, our compiled code can use a new\nlexical_address_assign\noperation instead of\nassign_symbol_value.\n\nWith lexical addressing, there is no need to include any symbolic references to names in the object code, and frames do not need to include symbols at run time.\n\nIn order to generate such code, the compiler must be able to determine\nthe lexical address of a\nname\nit is about to compile a reference\nto.\n\nThe lexical address of a\nname\nin a program depends on where\none is in the code.\n\nFor example, in the following program, the\naddress of in expression\n$e_1$ is (2,0) two frames back\nand the first\nname\nin the frame.\n\nAt that point\nis at\naddress (0,0) and is at address (1,2).\n\nIn expression\n$e_2$ ,\nis at (1,0),\nis at (1,1), and\nis at (0,2).\n\n```javascript\n((x, y) =>\n (a, b, c, d, e) =>\n ((y, z) => $e_1$)($e_2$, c + d + x))(3, 4);\n```\n\nOne way for the compiler to produce code that uses lexical addressing\nis to maintain a data structure called a\ncompile-time environment.\n\nThis keeps track of which\nbindings\nwill be at which\npositions in which frames in the runtime environment when a\nparticular\nname-access\noperation is executed.", + "token_count": 296, "has_code": true, "chapter": "Computing with Register Machines", - "section": "The Explicit-Control Evaluator", + "section": "Compilation", + "subsection": "Lexical Addressing", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Lexical_Addressing_2" + }, + { + "content": "This keeps track of which\nbindings\nwill be at which\npositions in which frames in the runtime environment when a\nparticular\nname-access\noperation is executed.\n\nThere will be no values associated with the symbols, since values are not computed at compile time.\n\n(Exercise will change this, as an optimization for constants.)\nThe compile-time\nenvironment becomes an additional argument to\nand is\npassed along to each code generator.\n\nThe top-level call to\nuses\na compile-time-environment that includes the names of all primitive functions and primitive values.\n\nWhen\nthe body of a lambda expression\nis compiled,\ncompile_lambda_body\nextends the compile-time environment by a frame containing the\nfunctions\nparameters, so that the\nbody is compiled with that extended environment.\n\nSimilarly, when the body of a block is compiled, compile_block extends the compile-time environment by a frame containing the scanned-out local names of the body.\n\nAt each point in the compilation,\ncompile_name\nand\ncompile_assignment_declaration\nuse the compile-time\nenvironment in order to generate the appropriate lexical addresses.\n\nExercises through describe how to complete this sketch of the lexical-addressing strategy in order to incorporate lexical lookup into the compiler.\n\nExercises and describe other uses for the compile-time environment.", + "token_count": 192, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Lexical Addressing", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_Lexical_Addressing_3" + }, + { + "content": "In section we modified our\noriginal metacircular interpreter to separate\nanalysis from execution.\n\nWe\nanalyzed each\ncomponent\nto produce an execution\nfunction\nthat took an environment as argument and performed the required operations.\n\nIn our compiler, we will do essentially the same analysis.\n\nInstead of\nproducing execution\nfunctions,\nhowever, we will generate sequences of instructions to be run by our\nregister machine.\n\nThe\nfunction\nis the top-level dispatch in the\ncompiler.\n\nIt corresponds to the evaluate\nfunction\nof section , the\nfunction\nof section , and the\neval_dispatch\nentry point of the explicit-control-evaluator in\nsection.\n\nThe compiler, like the\ninterpreters, uses the\ncomponent-syntax functions\ndefined in\nsection.\n\nThe function compile\nperforms a case analysis on the\nsyntactic type of the\ncomponent\nto be compiled.\n\nFor\neach type of\ncomponent,\nit dispatches to a\nspecialized\ncode generator :\n\n```javascript\ncompile\n\nfunction compile(component, target, linkage) {\n return is_literal(component)\n ? compile_literal(component, target, linkage)\n : is_name(component)\n ? compile_name(component, target, linkage)\n : is_application(component)\n ? compile_application(component, target, linkage)\n : is_operator_combination(component)\n ? compile(operator_combination_to_application(component),\n target, linkage)\n : is_conditional(component)\n ? compile_conditional(component, target, linkage)\n : is_lambda_expression(component)\n ? compile_lambda_expression(component, target, linkage)\n : is_sequence(component)\n ? compile_sequence(sequence_statements(component),\n target, linkage)\n : is_block(component)\n ? compile_block(component, target, linkage)\n : is_return_statement(component)\n ? compile_return_statement(component, target, linkage)\n : is_function_declaration(component)\n ? compile(function_decl_to_constant_decl(component),\n target, linkage)\n : is_declaration(component)\n ? compile_declaration(component, target, linkage)\n : is_assignment(component)\n ? compile_assignment(component, target, linkage)\n : error(component, \"unknown component type -- compile\");\n}\n```\n\nThe function compile\nand the code generators that it calls\ntake two\narguments in addition to the\ncomponent\nto compile.\n\nThere is a\ntarget , which specifies the register in which the compiled code is\nto return the value of the\ncomponent.\n\nThere is also a\nlinkage descriptor , which describes how the code resulting from the\ncompilation of the\ncomponent\nshould proceed when it has finished its\nexecution.", + "token_count": 294, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Structure of the Compiler", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Structure_of_the_Compiler_1" + }, + { + "content": "There is also a\nlinkage descriptor , which describes how the code resulting from the\ncompilation of the\ncomponent\nshould proceed when it has finished its\nexecution.\n\nFor example, compiling the literal with a target of the register and a linkage of \"next\" should produce the instruction\n\n```javascript\nassign(\"val\", constant(5))\n```\n\nCompiling the same expression with a linkage of \"return\" should produce the instructions\n\n```javascript\nassign(\"val\", constant(5)),\ngo_to(reg(\"continue\"))\n```\n\nIn the first case, execution will continue with the next instruction\nin the sequence.\n\nIn the second case,\nwe will jump to whatever entry point is stored in the continue register.\n\nIn both cases, the value of the expression will be placed into\nthe target register.\n\nOur compiler uses the \"return\" linkage when compiling the return expression of a return statement.\n\nJust as in the explicit-control evaluator, returning from a function call happens in three steps: reverting the stack to the marker and restoring continue (which holds a continuation set up at the beginning of the function call) computing the return value and placing it in val jumping to the entry point in continue Compilation of a return statement explicitly generates code for reverting the stack and restoring continue.\n\nThe return expression is compiled with target val and linkage \"return\" so that the generated code for computing the return value places the return value in val and ends by jumping to continue.\n\nEach code generator returns an\ninstruction sequence containing\nthe object code it has generated for the\ncomponent.\n\nCode generation for a\ncompound component\nis accomplished by combining the output from simpler code\ngenerators for\nsubcomponents,\njust as evaluation of a\ncompound component\nis accomplished by evaluating the\nsubcomponents.\n\nThe simplest method for combining instruction sequences is a\nfunction\ncalled\nappend_instruction_sequences,\nwhich takes as arguments two instruction sequences\nthat are to be\nexecuted\nsequentially.", + "token_count": 304, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Structure of the Compiler", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Structure_of_the_Compiler_2" + }, + { + "content": "The simplest method for combining instruction sequences is a\nfunction\ncalled\nappend_instruction_sequences,\nwhich takes as arguments two instruction sequences\nthat are to be\nexecuted\nsequentially.\n\nThat is, if $seq_1$ and\n$seq_2$ are sequences of instructions, then\nevaluating\n\n```javascript\nappend_instruction_sequences(seq$_1$, seq$_2$)\n```\n\nproduces the sequence\n\n```javascript\nseq$_1$\nseq$_2$\n```\n\nWhenever registers might need to be saved, the compiler s code\ngenerators use\n, which is a more subtle method for\ncombining instruction sequences.\n\nThe function preserving\ntakes three arguments: a set of registers and two instruction sequences that\nare to be executed sequentially.\n\nIt appends the sequences in such a way\nthat the contents of each register in the set is preserved over the\nexecution of the first sequence, if this is needed for the execution of the\nsecond sequence.\n\nThat is, if the first sequence modifies the register\nand the second sequence actually needs the register s original\ncontents, then wraps a\nand a\nof the register around the first sequence before appending the sequences.\n\nOtherwise, simply returns the\nappended instruction sequences.\n\nThus, for example,\n\n```javascript\npreserving(list(reg$_1$, reg$_2$), seq$_1$, seq$_2$)\n```\n\nproduces one of the following four sequences of instructions, depending on how seq $_1$ and seq $_2$ use reg $_1$ and reg $_2$ :\n\nBy using to combine instruction\nsequences the compiler avoids unnecessary\nstack operations.\n\nThis also\nisolates the details of whether or not to generate\nand\ninstructions within the\nfunction,\nseparating them from the concerns that arise in writing each of the\nindividual code generators.", + "token_count": 248, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Structure of the Compiler", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_Structure_of_the_Compiler_3" + }, + { + "content": "This also\nisolates the details of whether or not to generate\nand\ninstructions within the\nfunction,\nseparating them from the concerns that arise in writing each of the\nindividual code generators.\n\nIn principle, we could represent an instruction sequence simply as a\nlist of instructions.\n\nThe function append_instruction_sequences\ncould then combine instruction sequences by performing an ordinary list.\n\nHowever,\nwould then be a complex operation,\nbecause it would have to analyze each instruction sequence to\ndetermine how the sequence uses its registers.\n\nThe function preserving\nwould be inefficient as well as complex, because it would have to\nanalyze each of its instruction sequence arguments, even though these\nsequences might themselves have been constructed by calls to\n, in which case their parts would\nhave already been analyzed.\n\nTo avoid such repetitious analysis we will\nassociate with each instruction sequence some information about its register\nuse.\n\nWhen we construct a basic instruction sequence we\nwill provide this information explicitly,\nand the\nfunctions\nthat combine instruction sequences will derive\nregister-use information for the combined sequence from the\ninformation associated with the sequences being combined.\n\nAn instruction sequence will contain three pieces of information:\n-\n- the set of registers that must be initialized before the\ninstructions in the sequence are executed (these registers are said to\nbe needed by the sequence),\n-\n- the set of registers whose values are modified by the\ninstructions in the sequence, and\n-\n- the actual instructions\nin the sequence.\n\nWe will represent an instruction sequence as a list of its three\nparts.\n\nThe constructor for instruction sequences is thus\n\n```javascript\nmake_instruction_sequence\n\nfunction make_instruction_sequence(needs, modifies, instructions) {\n return list(needs, modifies, instructions);\n}\n```", + "token_count": 277, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Structure of the Compiler", + "chunk_index": 4, + "chunk_id": "Computing_with_Register_Machines_Structure_of_the_Compiler_4" + }, + { + "content": "The constructor for instruction sequences is thus\n\nThis sequence would therefore be constructed as\n\n```javascript\nmake_instruction_sequence(list(\"env\", \"continue\"), list(\"val\"),\n list(assign(\"val\",\n list(op(\"lookup_symbol_value\"), constant(\"x\"),\n reg(\"env\"))),\n go_to(reg(\"continue\"))));\n```\n\nThe functions for combining instruction sequences are shown in section.", + "token_count": 34, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Compilation", + "subsection": "Structure of the Compiler", + "chunk_index": 5, + "chunk_id": "Computing_with_Register_Machines_Structure_of_the_Compiler_5" + }, + { + "content": "To design a register machine, we must design its data paths\n(registers and operations) and the controller that sequences\nthese operations.\n\nTo illustrate the design of a simple register\nmachine, let us examine Euclid s Algorithm, which is used to compute\nthe greatest common divisor (GCD) of two integers.\n\nAs we saw in\nsection ,\nEuclid s Algorithm can be\ncarried out by an iterative process, as specified by the following\nfunction:\n\n```javascript\ngcd_example\n\nfunction gcd(a, b) {\n return b === 0 ? a : gcd(b, a % b);\n}\n```\n\nA machine to carry out this algorithm must keep track of two numbers,\n$a$ and $b$ , so let us\nassume that these numbers are stored in two registers with those names.\n\nThe\nbasic operations required are testing whether the contents of register\nis zero and computing the remainder of the\ncontents of register divided by the contents\nof register.\n\nThe remainder operation is a complex process, but assume for the moment that\nwe have a primitive device that computes remainders.\n\nOn each cycle of the\nGCD algorithm, the contents of register must\nbe replaced by the contents of register , and\nthe contents of must be replaced by the\nremainder of the old contents of divided by\nthe old contents of.\n\nIt would be convenient\nif these replacements could be done simultaneously, but in our model of\nregister machines we will assume that only one register can be assigned a\nnew value at each step.\n\nTo accomplish the replacements, our machine will use\na third temporary register, which we call.\n\n(First the remainder will be placed in\n, then the contents of\nwill be placed in\n, and finally the remainder stored in\nwill be placed in.)", + "token_count": 289, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Designing_Register_Machines_1" + }, + { + "content": "(First the remainder will be placed in\n, then the contents of\nwill be placed in\n, and finally the remainder stored in\nwill be placed in.)\n\nIn this\ndiagram, the registers (,\n, and ) are\nrepresented by rectangles.\n\nEach way to assign a value to a register is\nindicated by an arrow with a buttondrawn as $\\otimes$ behind the\nhead, pointing from the source of data to the register.\n\nWhen pushed, the button allows\nthe value at the source to flow into the designated register.\n\nThe label next to each button is the name we will use to refer to the\nbutton.\n\nThe names are arbitrary, and can be chosen to have mnemonic value\n(for example, denotes pushing the\nbutton that assigns the contents of register\nto register ).\n\nThe source of data for a\nregister can be another register (as in the\nassignment), an operation result (as in\nthe assignment), or a constant\n(a built-in value that cannot be changed, represented in a data-path\ndiagram by a triangle containing the constant).\n\nAn operation that computes a value from constants and the contents\nof registers is represented in a data-path diagram by a trapezoid\ncontaining a name for the operation.\n\nFor example, the box marked\nin\nfigure represents an operation that\ncomputes the remainder of the contents of the registers\nand to which\nit is attached.\n\nArrows (without buttons) point from the input registers and\nconstants to the box, and arrows connect the operation s output value\nto registers.\n\nA test is represented by a circle containing a name for the\ntest.\n\nFor example, our GCD machine has an operation that tests whether the\ncontents of register is zero.", + "token_count": 281, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", "subsection": null, "chunk_index": 2, - "chunk_id": "Computing_with_Register_Machines_The_Explicit-Control_Evaluator_2" + "chunk_id": "Computing_with_Register_Machines_Designing_Register_Machines_2" }, { - "content": "In our study of program design, we have seen that expert programmers\ncontrol the complexity of their designs with the same general\ntechniques used by designers of all complex systems.\n\nThey combine\nprimitive elements to form compound objects, they abstract compound\nobjects to form higher-level building blocks, and they preserve\nmodularity by adopting appropriate large-scale views of system\nstructure.\n\nIn illustrating these techniques, we have used\nJavaScript\nas a language for describing processes and for constructing computational\ndata objects and processes to model complex phenomena in the real world.\n\nHowever, as we confront increasingly complex problems, we will find that\nJavaScript,\nor indeed any fixed programming language, is not sufficient for our needs.\n\nWe must constantly turn to new languages in order to express our ideas more\neffectively.\n\nEstablishing new languages is a powerful strategy for\ncontrolling complexity in engineering design; we can often enhance our\nability to deal with a complex problem by adopting a new language that\nenables us to describe (and hence to think about) the problem in a different\nway, using primitives, means of combination, and means of abstraction that\nare particularly well suited to the problem at hand.\n\nProgramming is endowed with a multitude of languages.\n\nThere are\nphysical languages, such as the\nfunction declaration,\nthat are appropriate to the larger-scale organization of systems.\n\nMetalinguistic abstraction establishing\nplays an important role in all branches of engineering\ndesign.\n\nIt is particularly important to computer programming, because\nin programming not only can we formulate new languages but we can also\nimplement these languages by constructing evaluators.\n\nAn\nevaluator (or interpreter ) for a programming language is a\nfunction\nthat, when applied to\na statement or expression\nof the language, performs the actions required to evaluate that\nstatement or\nexpression.", - "token_count": 292, + "content": "For example, our GCD machine has an operation that tests whether the\ncontents of register is zero.\n\nOverall, the data-path diagram shows the registers and\noperations that are required for the machine and how they must be\nconnected.\n\nIf we view the arrows as wires and the\n$\\otimes$ buttons as switches, the data-path diagram\nis very like the wiring diagram for a machine that could be constructed\nfrom electrical components.\n\nData paths for a GCD machine.\n\nIn order for the data paths to actually compute GCDs, the buttons must\nbe pushed in the correct sequence.\n\nWe will describe this sequence in\nterms of a\ncontroller diagram, as illustrated in\nfigure.\n\nThe elements of the\ncontroller diagram indicate how the data-path components should be operated.\n\nThe rectangular boxes in the controller diagram identify data-path buttons\nto be pushed, and the arrows describe the sequencing from one step to the\nnext.\n\nThe diamond in the diagram represents a decision.\n\nOne of the two\nsequencing arrows will be followed, depending on the value of the data-path\ntest identified in the diamond.\n\nWe can interpret the controller in terms\nof a physical analogy: Think of the diagram as a maze in which a marble is\nrolling.\n\nWhen the marble rolls into a box, it pushes the data-path button\nthat is named by the box.\n\nWhen the marble rolls into a decision node (such\nas the test for\n$\\, =0$ ), it leaves\nthe node on the path determined by the result of the indicated test.\n\nTaken together, the data paths and the controller completely describe\na machine for computing GCDs.\n\nWe start the controller (the rolling\nmarble) at the place marked , after\nplacing numbers in registers and.\n\nWhen the controller reaches\n, we will find the value of the GCD in\nregister.", + "token_count": 299, "has_code": false, - "chapter": "Metalinguistic Abstraction", - "section": null, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", "subsection": null, + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_Designing_Register_Machines_3" + }, + { + "content": "Data-path and controller diagrams are adequate for representing simple\nmachines such as GCD, but they are unwieldy for describing large\nmachines such as a\nJavaScript\ninterpreter.\n\nTo make it possible to deal with complex machines, we will\ncreate a language that presents, in textual form, all the\ninformation given by the data-path and controller\ndiagrams.\n\nWe will start with a notation that directly\nmirrors the diagrams.\n\nWe define the data paths of a machine by describing the registers and\nthe operations.\n\nTo describe a register, we give it a name\nand specify the buttons that control assignment to it.\n\nWe give each\nof these buttons a name and specify the source of the data that enters\nthe register under the button s control.\n\n(The source is a register,\na constant, or an operation.) To describe an operation, we give\nit a name and specify its inputs (registers or constants).\n\nWe define the controller of a machine as a sequence of\ninstructions together with\nlabels that identify\nentry points in the sequence.\n\nAn instruction is one of the following:\n-\n-\nThe name of a data-path button to push to assign a value to\na register.\n\n(This corresponds to a box in the controller diagram.)\n-\n-\nA\ninstruction, which performs a\nspecified test.\n-\n-\nA\nconditional branch ( instruction)\nto a location indicated by a controller label, based on the result of\nthe previous test.\n\n(The test and branch together correspond to a\ndiamond in the controller diagram.) If the test is false, the\ncontroller should continue with the next instruction in the sequence.\n\nOtherwise, the controller should continue with the instruction after\nthe label.\n-\n-\nAn\nunconditional branch\n(go_to\ninstruction) naming a controller label at which to continue execution.\n\nController for a GCD machine.", + "token_count": 297, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "A Language for Describing Register Machines", "chunk_index": 1, - "chunk_id": "Metalinguistic_Abstraction_Metalinguistic_Abstraction_1" + "chunk_id": "Computing_with_Register_Machines_A_Language_for_Describing_Register_Machines_1" + }, + { + "content": "Controller for a GCD machine.\n\nExcept when a branch changes the flow of control, instructions are\nexecuted in the order in which they are listed.\n\nFigure shows the GCD machine\ndescribed in this way.\n\nThis example only hints at the generality of these\ndescriptions, since the GCD machine is a very simple case: Each register has\nonly one button, and each button and test is used only once in the\ncontroller.\n\nUnfortunately, it is difficult to read such a description.\n\nIn order\nto understand the controller instructions we must constantly refer\nback to the definitions of the button names and the operation names,\nand to understand what the buttons do we may have to refer to the\ndefinitions of the operation names.\n\nWe will thus transform our\nnotation to combine the information from the data-path and controller\ndescriptions so that we see it all together.\n\nTo obtain this form of description, we will replace the arbitrary\nbutton and operation names by the definitions of their behavior.\n\nThat\nis, instead of saying (in the controller) Push button\nand separately saying (in the\ndata paths) Button assigns the\nvalue of the operation to register\nand The\noperation s inputs are the contents\nof registers\nand ,\nwe will say (in the controller) Push the button that assigns to\nregister the value of the\noperation on the contents of registers\nand.\n\nSimilarly, instead of saying (in the controller) Perform the\ntest and separately saying (in the\ndata paths) The test operates on the\ncontents of register and the\nconstant 0, we will say Perform the\ntest on the\ncontents of register and the\nconstant 0.\n\nWe will omit the data-path description, leaving only\nthe controller sequence.\n\nThus, the GCD machine is described as follows:", + "token_count": 291, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "A Language for Describing Register Machines", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_A_Language_for_Describing_Register_Machines_2" + }, + { + "content": "Thus, the GCD machine is described as follows:\n\n```javascript\ngcd_controller_declaration\n\nconst gcd_controller =\n```\n\n```javascript\ngcd_controller_construction\n gcd_controller_example\n 2\n\ncontroller(\n list(\n \"test_b\",\n test(list(op(\"=\"), reg(\"b\"), constant(0))),\n branch(label(\"gcd_done\")),\n assign(\"t\", list(op(\"rem\"), reg(\"a\"), reg(\"b\"))),\n assign(\"a\", reg(\"b\")),\n assign(\"b\", reg(\"t\")),\n go_to(label(\"test_b\")),\n \"gcd_done\"))\n```\n\n```javascript\ncontroller\n\nfunction controller(sequence) {\n return list(\"controller\", sequence);\n}\nfunction controller_sequence(controller) {\n return head(tail(controller));\n}\n```\n\nThis form of description is easier to read than the kind illustrated\nin figure , but it also has disadvantages:\n-\n- It is more verbose for large machines,\nbecause complete descriptions of the data-path elements are repeated\nwhenever the elements are mentioned in the controller instruction\nsequence.\n\n(This is not a problem in the GCD example, because each\noperation and button is used only once.) Moreover, repeating the\ndata-path descriptions obscures the actual data-path structure of the\nmachine; it is not obvious for a large machine how many registers,\noperations, and buttons there are and how they are interconnected.\n-\n-\nBecause the controller instructions in a machine definition look like\nJavaScript\nexpressions, it is easy to forget that they are\nnot arbitrary\nJavaScript\nexpressions.\n\nThey can notate only legal machine operations.\n\nFor\nexample, operations can operate directly only on constants and the\ncontents of registers, not on the results of other operations.\n\nIn spite of these disadvantages, we will use this register-machine\nlanguage throughout this chapter, because we will be more concerned with\nunderstanding controllers than with understanding the elements and\nconnections in data paths.\n\nWe should keep in mind,\nhowever, that data-path design is crucial in designing real machines.\n\nLet us modify the GCD machine so that we can type in the numbers\nwhose GCD we want and get the answer\nprinted.", + "token_count": 277, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "A Language for Describing Register Machines", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_A_Language_for_Describing_Register_Machines_3" + }, + { + "content": "Let us modify the GCD machine so that we can type in the numbers\nwhose GCD we want and get the answer\nprinted.\n\nThe operation prompt\nis like the operations we have been using in that it produces a value that\ncan be stored in a register.\n\nBut\nprompt\ndoes not take inputs from any registers; its value depends on\nsomething that happens outside the parts of the machine we are\ndesigning.\n\nWe will allow our machine s operations to have such\nbehavior, and thus will draw and notate the use of\nprompt\njust as we do any other operation that computes a value.\n\nThe operation display,\non the other hand, differs from the operations we have\nbeen using in a fundamental way: It does not produce an output value\nto be stored in a register.\n\nThough it has an effect, this effect is\nnot on a part of the machine we are designing.\n\nWe will refer to this\nkind of operation as an action.\n\nWe will represent an action in\na data-path diagram just as we represent an operation that computes a\nvalue as a trapezoid that contains the name of the action.\n\nArrows point to the action box from any inputs (registers or\nconstants).\n\nWe also associate a button with the action.\n\nPushing the\nbutton makes the action happen.\n\nTo make a controller push an action\nbutton we use a new kind of instruction called.\n\nThus,\nthe action of printing\nthe contents of register\nis represented\nin a controller sequence by the instruction\n\n```javascript\nperform(list(op(\"display\"), reg(\"a\")))\n```\n\nFigure\nshows the data paths and controller for\nthe new GCD machine.", + "token_count": 271, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "A Language for Describing Register Machines", + "chunk_index": 4, + "chunk_id": "Computing_with_Register_Machines_A_Language_for_Describing_Register_Machines_4" + }, + { + "content": "Figure\nshows the data paths and controller for\nthe new GCD machine.\n\nThis structure is like the driver loops we used in the interpreters of\nchapter.\ngcd_with_prompt gcd_with_prompt_example controller( list( \"gcd_loop\", assign(\"a\", list(op(\"prompt\"))), assign(\"b\", list(op(\"prompt\"))), \"test_b\", test(list(op(\"=\"), reg(\"b\"), constant(0))), branch(label(\"gcd_done\")), assign(\"t\", list(op(\"rem\"), reg(\"a\"), reg(\"b\"))), assign(\"a\", reg(\"b\")), assign(\"b\", reg(\"t\")), go_to(label(\"test_b\")), \"gcd_done\", perform(list(op(\"display\"), reg(\"a\"))), go_to(label(\"gcd_loop\")))) gcd_with_prompt_example controller make_machine start gcd_with_prompt_declaration ; const gcd_with_prompt_machine = make_machine( list(\"a\", \"b\", \"t\"), list(list(\"rem\", (a, b) => a % b), list(\"=\", (a, b) => a === b), list(\"prompt\", () => parse_int(prompt(\"enter number:\"), 10)), list(\"display\", display)), controller_sequence(gcd_with_prompt_controller)); start(gcd_with_prompt_machine); gcd_with_prompt_declaration const gcd_with_prompt_controller = A GCD machine that reads inputs and prints results.", + "token_count": 103, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "A Language for Describing Register Machines", + "chunk_index": 5, + "chunk_id": "Computing_with_Register_Machines_A_Language_for_Describing_Register_Machines_5" + }, + { + "content": "We will often define a machine to include primitive\noperations that are actually very complex.\n\nFor example, in\nsections and\nwe will treat\nJavaScripts\nenvironment manipulations as primitive.\n\nSuch abstraction is valuable\nbecause it allows us to ignore the details of parts of a machine so that we\ncan concentrate on other aspects of the design.\n\nThe fact that we have\nswept a lot of complexity under the rug, however, does not mean that a\nmachine design is unrealistic.\n\nWe can always replace the complex\nprimitives by simpler primitive operations.\n\nConsider the GCD machine.\n\nThe machine has an instruction that computes\nthe remainder of the contents of registers\nand and assigns the result to register.\n\nIf we want to construct the GCD machine\nwithout using a primitive remainder operation, we must specify how to\ncompute remainders in terms of simpler operations, such as subtraction.\n\nIndeed, we can write a\nJavaScript function\nthat finds remainders in this way:\n\n```javascript\nremainder_example\n\nremainder(29, 5);\n```\n\n```javascript\nremainder\n remainder_example\n\nfunction remainder(n, d) {\n return n < d\n ? n\n : remainder(n - d, d);\n}\n```\n\nWe can thus replace the remainder operation in the GCD machine s\ndata paths with a subtraction operation and a comparison test.\n\nFigure shows the data paths and\ncontroller for the elaborated machine.\n\nThe instruction\nData paths and controller for the elaborated GCD machine.\n\n```javascript\nassign(\"t\", list(op(\"rem\"), reg(\"a\"), reg(\"b\")))\n```\n\nin the GCD controller definition is replaced by a sequence of instructions that contains a loop, as shown in figure.\n\n```javascript\ngcd_elaborated\n\tgcd_elaborated_example\n\t2\n\ncontroller(\n list(\n \"test_b\",\n test(list(op(\"=\"), reg(\"b\"), constant(0))),\n branch(label(\"gcd_done\")),\n assign(\"t\", reg(\"a\")),\n \"rem_loop\",\n test(list(op(\"<\"), reg(\"t\"), reg(\"b\"))),\n branch(label(\"rem_done\")),\n assign(\"t\", list(op(\"-\"), reg(\"t\"), reg(\"b\"))),\n go_to(label(\"rem_loop\")),\n \"rem_done\",\n assign(\"a\", reg(\"b\")),\n assign(\"b\", reg(\"t\")),\n go_to(label(\"test_b\")),\n \"gcd_done\"))\n```", + "token_count": 283, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "Abstraction in Machine Design", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Abstraction_in_Machine_Design_1" + }, + { + "content": "in the GCD controller definition is replaced by a sequence of instructions that contains a loop, as shown in figure.\n\n```javascript\ngcd_elaborated_declaration\n\nconst gcd_elaborated_controller =\n```\n\nController instruction sequence for the GCD machine in figure.", + "token_count": 35, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "Abstraction in Machine Design", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Abstraction_in_Machine_Design_2" + }, + { + "content": "A controller instruction in our register-machine language has one of the following forms, where each input $_i$ is reg(register-name) or constant(constant-value).\n\nThese instructions were introduced in section :\n\n```javascript\nassign(register-name, reg(register-name))\n\nassign(register-name, constant(constant-value))\n\nassign(register-name, list(op(operation-name), input$_1$, $\\ldots$, input$_n$))\n\nperform(list(op(operation-name), input$_1$, $\\ldots$, input$_n$))\n\ntest(list(op(operation-name), input$_1$, $\\ldots$, input$_n$))\n\nbranch(label(label-name))\n\ngo_to(label(label-name))\n```\n\nThe use of registers to hold labels was introduced in section :\n\n```javascript\nassign(register-name, label(label-name))\n\ngo_to(reg(register-name))\n```\n\nInstructions to use the stack were introduced in section :\n\n```javascript\nsave(register-name)\n\nrestore(register-name)\n```\n\nThe only kind of\nconstant-value\nwe have seen so far is a number, but later we will\nalso use strings\nand lists.\n\nFor example, constant(\"abc\") is the string , constant(null) is the empty list, and constant(list(\"a\", \"b\", \"c\")) is the list list(\"a\", \"b\", \"c\").", + "token_count": 124, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "Instruction Summary", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Instruction_Summary_1" + }, + { + "content": "When designing a machine to perform a computation, we would often\nprefer to arrange for components to be shared by different parts of\nthe computation rather than duplicate the components.\n\nConsider a\nmachine that includes two GCD computations one that finds the GCD of\nthe contents of registers and\nand one that finds the\nGCD of the contents of registers and.\n\nWe might start\nby assuming we have a primitive operation,\nthen expand the two instances of in terms\nof more primitive operations.\n\nFigure\nshows just the GCD portions of the resulting machine s data paths,\nwithout showing how they connect to the rest of the machine.\n\nThe figure\nalso shows the corresponding portions of the machine s controller\nsequence.\n\nPortions of the data paths and controller sequence for a machine with two GCD computations.\n\nThis machine has two remainder operation boxes and two boxes for\ntesting equality.\n\nIf the duplicated components are complicated, as is the\nremainder box, this will not be an economical way to build the\nmachine.\n\nWe can avoid duplicating the data-path components by using\nthe same components for both GCD computations, provided that doing so\nwill not affect the rest of the larger machine s computation.\n\nIf the\nvalues in registers and\nare not needed by the time the\ncontroller gets to gcd_2 (or if these values\ncan be moved to other registers for safekeeping), we can change the machine\nso that it uses registers and\n, rather than registers\nand , in\ncomputing the second GCD as well as the first.\n\nIf we do this, we obtain the\ncontroller sequence shown in\nfigure.", + "token_count": 269, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "Subroutines", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Subroutines_1" + }, + { + "content": "If we do this, we obtain the\ncontroller sequence shown in\nfigure.\n\nIt would be better to replace these two sequences by branches to a\nsingle sequence a\nsubroutine at the end of which we branch back to the\ncorrect place in the main instruction sequence.\n\nWe can accomplish this as\nfollows: Before branching to , we place a\ndistinguishing value (such as 0 or 1) into a special register,.\n\nAt the end of the\nsubroutine we return either to\nafter_gcd_1 or to after_gcd_2 , depending\non the value of the register.\n\nFigure shows the relevant portion\nof the resulting controller sequence, which includes only a single copy of\nthe instructions.\n\n```javascript\n\"gcd_1\",\n test(list(op(\"=\"), reg(\"b\"), constant(0))),\n branch(label(\"after_gcd_1\")),\n assign(\"t\", list(op(\"rem\"), reg(\"a\"), reg(\"b\"))),\n assign(\"a\", reg(\"b\")),\n assign(\"b\", reg(\"t\")),\n go_to(label(\"gcd_1\")),\n\"after_gcd_1\",\n $\\vdots$\n\"gcd_2\",\n test(list(op(\"=\"), reg(\"b\"), constant(0))),\n branch(label(\"after_gcd_2\")),\n assign(\"t\", list(op(\"rem\"), reg(\"a\"), reg(\"b\"))),\n assign(\"a\", reg(\"b\")),\n assign(\"b\", reg(\"t\")),\n go_to(label(\"gcd_2\")),\n\"after_gcd_2\"\n```\n\nPortions of the controller sequence for a machine that uses the same data-path components for two different GCD computations.\n\n```javascript\n\"gcd\",\n test(list(op(\"=\"), reg(\"b\"), constant(0))),\n branch(label(\"gcd_done\")),\n assign(\"t\", list(op(\"rem\"), reg(\"a\"), reg(\"b\"))),\n assign(\"a\", reg(\"b\")),\n assign(\"b\", reg(\"t\")),\n go_to(label(\"gcd\")),\n\"gcd_done\",\n test(list(op(\"=\"), reg(\"continue\"), constant(0))),\n branch(label(\"after_gcd_1\")),\n go_to(label(\"after_gcd_2\")),\n $\\vdots$\n // Before branching to $\\texttt{gcd}$ from the first place where\n // it is needed, we place 0 in the $\\texttt{continue}$ register\n assign(\"continue\", constant(0)),\n go_to(label(\"gcd\")),\n\"after_gcd_1\",\n $\\vdots$\n // Before the second use of $\\texttt{gcd}$, we place 1 in the $\\texttt{continue}$ register\n assign(\"continue\", constant(1)),\n go_to(label(\"gcd\")),\n\"after_gcd_2\"\n```\n\nUsing a register to avoid the duplicate controller sequence in figure.\n\nThis is a reasonable approach for handling small problems, but it would be\nawkward if there were many instances of GCD computations in the controller\nsequence.\n\nTo decide where to continue executing after the\nsubroutine, we would need tests in the data\npaths and branch instructions in the controller for all the places that use.", + "token_count": 296, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "Subroutines", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Subroutines_2" + }, + { + "content": "To decide where to continue executing after the\nsubroutine, we would need tests in the data\npaths and branch instructions in the controller for all the places that use.\n\nImplementing this\nstrategy requires a new kind of connection between the data paths and the\ncontroller of a register machine: There must be a way to assign to a\nregister a label in the controller sequence in such a way that this value\ncan be fetched from the register and used to continue execution at the\ndesignated entry point.\n\nTo reflect this ability, we will extend the\ninstruction of the register-machine language to allow a register to be\nassigned as value a label from the controller sequence (as a special\nkind of constant).\n\nWe will also extend the\ngo_to\ninstruction to allow execution to continue at the entry point described by\nthe contents of a register rather than only at an entry point described by\na constant label.\n\nUsing these new constructs we can terminate the\nsubroutine with a branch to the location\nstored in the register.\n\nThis leads\nto the controller sequence shown in\nfigure.\n\nA machine with more than one subroutine could use multiple\ncontinuation registers (e.g., gcd_continue ,\nfactorial_continue ) or we could have all\nsubroutines share a single\nregister.\n\nSharing is more economical,\nbut we must be careful if we have a subroutine\n() that calls another subroutine\n().\n\nUnless\nsaves the contents of\nin some other register before setting\nup for the call to\n, will\nnot know where to go when it is finished.\n\nThe mechanism developed in the\nnext section to handle recursion also provides a better solution to this\nproblem of nested subroutine calls.", + "token_count": 281, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "Subroutines", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_Subroutines_3" + }, + { + "content": "With the ideas illustrated so far, we can implement any\niterative\nprocess by specifying a register machine that has a register\ncorresponding to each state variable of the process.\n\nThe machine\nrepeatedly executes a controller loop, changing the contents\nof the registers, until some termination condition is satisfied.\n\nAt\neach point in the controller sequence, the state of the machine\n(representing the state of the iterative process) is completely\ndetermined by the contents of the registers (the values of the state\nvariables).\n\nImplementing\nrecursive processes, however, requires an additional\nmechanism.\n\nConsider the following recursive method for computing\nfactorials, which we first examined in\nsection :\n\n```javascript\nfactorial_5_1_4\n factorial_example\n 120\n\nfunction factorial(n) {\n return n === 1\n ? 1\n : n * factorial(n - 1);\n}\n```\n\nAs we see from the\nfunction,\ncomputing $n!$ requires computing\n$(n-1)!$.\n\nOur GCD machine, modeled on the\nfunction\n\n```javascript\ngcd_5_1_4_example\n\ngcd(20, 12);\n```\n\n```javascript\ngcd_5_1_4\n gcd_5_1_4_example\n 4\n\nfunction gcd(a, b) {\n return b === 0 ? a : gcd(b, a % b);\n}\n```\n\nsimilarly had to compute another GCD.\n\nBut there is an important\ndifference between the\nfunction,\nwhich reduces the original computation to a new GCD computation, and\n, which requires computing another\nfactorial as a subproblem.\n\nIn GCD, the answer to the new GCD computation is\nthe answer to the original problem.\n\nTo compute the next GCD, we simply\nplace the new arguments in the input registers of the GCD machine and reuse\nthe machine s data paths by executing the same controller sequence.\n\nWhen the machine is finished solving the final GCD problem, it has completed\nthe entire computation.\n\nIn the case of factorial (or any recursive process) the answer to the\nnew factorial subproblem is not the answer to the original problem.", + "token_count": 293, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "Using a Stack to Implement Recursion", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Using_a_Stack_to_Implement_Recursion_1" + }, + { + "content": "In the case of factorial (or any recursive process) the answer to the\nnew factorial subproblem is not the answer to the original problem.\n\nIf\nwe try to imitate the GCD design, and solve the factorial subproblem by\ndecrementing the register and rerunning the\nfactorial machine, we will no longer have available the old value of\nby which to multiply the result.\n\nWe thus\nneed a second factorial machine to work on the subproblem.\n\nThis second\nfactorial computation itself has a factorial subproblem, which\nrequires a third factorial machine, and so on.\n\nSince each factorial\nmachine contains another factorial machine within it, the total\nmachine contains an infinite nest of similar machines and hence cannot\nbe constructed from a fixed, finite number of parts.\n\nNevertheless, we can implement the factorial process as a register\nmachine if we can arrange to use the same components for each nested\ninstance of the machine.\n\nSpecifically, the machine that computes\n$n!$\nshould use the same components to work on the subproblem of computing\n$(n-1)!$ , on the subproblem for\n$(n-2)!$ , and so on.\n\nThis is\nplausible because, although the factorial process dictates that an\nunbounded number of copies of the same machine are needed to perform a\ncomputation, only one of these copies needs to be active at any given\ntime.\n\nWhen the machine encounters a recursive subproblem, it can\nsuspend work on the main problem, reuse the same physical parts to\nwork on the subproblem, then continue the suspended computation.\n\nIn the subproblem, the contents of the registers will be different\nthan they were in the main problem.", + "token_count": 266, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "Using a Stack to Implement Recursion", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Using_a_Stack_to_Implement_Recursion_2" + }, + { + "content": "In the subproblem, the contents of the registers will be different\nthan they were in the main problem.\n\nIn the case of factorial, we will save the old value of\n, to be restored when we are finished\ncomputing the factorial of the decremented\nregister.\n\nSince there is no a priori limit on the depth of nested\nrecursive calls, we may need to save an arbitrary number of register\nvalues.\n\nThese values must be restored in the reverse of the order in\nwhich they were saved, since in a nest of recursions the last\nsubproblem to be entered is the first to be finished.\n\nThis dictates\nthe use of a stack , or last in, first out data\nstructure, to save register values.\n\nWe can extend the register-machine\nlanguage to include a stack by adding two kinds of instructions: Values are\nplaced\non the stack using a\ninstruction and\nrestored from the stack using a\ninstruction.\n\nAfter a sequence of values has been\nd on the stack, a sequence of\ns will retrieve these values in reverse\norder.\n\nWith the aid of the stack, we can reuse a single copy of the factorial\nmachine s data paths for each factorial subproblem.\n\nThere is a\nsimilar design issue in reusing the controller sequence that operates\nthe data paths.\n\nTo reexecute the factorial computation, the\ncontroller cannot simply loop back to the beginning, as with\nan iterative process, because after solving the\n$(n-1)!$ subproblem\nthe machine must still multiply the result by\n$n$.\n\nThe controller\nmust suspend its computation of $n!$ , solve the\n$(n-1)!$ subproblem,\nthen continue its computation of $n!$.", + "token_count": 270, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "Using a Stack to Implement Recursion", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_Using_a_Stack_to_Implement_Recursion_3" + }, + { + "content": "The controller\nmust suspend its computation of $n!$ , solve the\n$(n-1)!$ subproblem,\nthen continue its computation of $n!$.\n\nWe can thus make a factorial subroutine that returns to\nthe entry point stored in the\nregister.\n\nAround each subroutine call, we save and restore\njust as we do the\nregister, since each level of\nthe factorial computation will use the same\nregister.\n\nThat is, the factorial\nsubroutine must put a new value in\nwhen it calls itself for a subproblem, but it will need the old value in\norder to return to the place that called it to solve a subproblem.\n\nFigure\nshows the data paths and controller for\na machine that implements the recursive\nfunction.\n\nThe machine has a stack and three registers, called\n, , and.\n\nTo simplify the data-path diagram,\nwe have not named the register-assignment buttons, only the stack-operation\nbuttons ( and\nto save registers, and\nto restore registers).\n\nTo operate the\nmachine, we put in register the number whose\nfactorial we wish to compute and start the machine.\n\nWhen the machine\nreaches fact_done , the computation is finished\nand the answer will be found in the\nregister.\n\nIn the controller sequence, and\nare saved before each recursive call\nand restored upon return from the call.\n\nReturning from a call is\naccomplished by branching to the location stored in.\n\nThe register\nis initialized when the machine starts so that the last return will go to\nfact_done.\n\nThe\nregister, which holds the result of the factorial computation, is not\nsaved before the recursive call, because the old contents of\nis not useful after the subroutine returns.\n\nOnly the new value, which is the value produced by the subcomputation, is\nneeded.", + "token_count": 283, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "Using a Stack to Implement Recursion", + "chunk_index": 4, + "chunk_id": "Computing_with_Register_Machines_Using_a_Stack_to_Implement_Recursion_4" + }, + { + "content": "Only the new value, which is the value produced by the subcomputation, is\nneeded.\n\nAny\nparticular physical implementation of a stack, however, will be of finite\nsize, and this will limit the depth of recursive calls that can be handled\nby the machine.\n\nThis implementation of factorial illustrates the general\nstrategy for realizing recursive algorithms as ordinary register machines\naugmented by stacks.\n\nWhen a recursive subproblem is encountered, we save on\nthe stack the registers whose current values will be required after the\nsubproblem is solved, solve the recursive subproblem, then restore the saved\nregisters and continue execution on the main problem.\n\nThe\nregister must always be saved.\n\nWhether there are other registers that need to be saved depends on the\nparticular machine, since not all recursive computations need the original\nvalues of registers that are modified during solution of the subproblem\n(see exercise ).\n\nLet us examine a more complex recursive process, the tree-recursive computation of the Fibonacci numbers, which we introduced in section :\n\n```javascript\nfib_5_1_4_example\n\nfib(6);\n```\n\n```javascript\nfib_5_1_4\n fib_5_1_4_example\n 8\n\nfunction fib(n) {\n return n === 0\n ? 0\n : n === 1\n ? 1\n : fib(n - 1) + fib(n - 2);\n}\n```\n\nJust as with factorial, we can implement the recursive Fibonacci\ncomputation as a register machine with registers\n, ,\nand.\n\nThe machine is more complex than\nthe one for factorial, because there are two places in the controller\nsequence where we need to perform recursive calls once to compute\nFib $(n-1)$ and once to compute\nFib $(n-2)$.", + "token_count": 256, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "Using a Stack to Implement Recursion", + "chunk_index": 5, + "chunk_id": "Computing_with_Register_Machines_Using_a_Stack_to_Implement_Recursion_5" + }, + { + "content": "The machine is more complex than\nthe one for factorial, because there are two places in the controller\nsequence where we need to perform recursive calls once to compute\nFib $(n-1)$ and once to compute\nFib $(n-2)$.\n\nWe then go to\nfib_loop.\n\nWhen we return from the\nrecursive call, the answer is in.\n\nFigure shows the controller sequence\nfor this machine.\n\n```javascript\nfib_recursive_controller\n\tfib_recursive_example\n\t8\n\ncontroller(\n list(\n assign(\"continue\", label(\"fib_done\")),\n \"fib_loop\",\n test(list(op(\"<\"), reg(\"n\"), constant(2))),\n branch(label(\"immediate_answer\")),\n // set up to compute $\\textrm{Fib}(n-1)$\n save(\"continue\"),\n assign(\"continue\", label(\"afterfib_n_1\")),\n save(\"n\"), // save old value of $\\texttt{n}$\n assign(\"n\", list(op(\"-\"), reg(\"n\"), constant(1))), // clobber $\\texttt{n}$ to $n-1$\n go_to(label(\"fib_loop\")), // perform recursive call\n \"afterfib_n_1\", // upon return, $\\texttt{val}$ contains $\\textrm{Fib}(n-1)$\n restore(\"n\"),\n restore(\"continue\"),\n // set up to compute $\\textrm{Fib}(n-2)$\n assign(\"n\", list(op(\"-\"), reg(\"n\"), constant(2))),\n save(\"continue\"),\n assign(\"continue\", label(\"afterfib_n_2\")),\n save(\"val\"), // save $\\textrm{Fib}(n-1)$\n go_to(label(\"fib_loop\")),\n \"afterfib_n_2\", // upon return, $\\texttt{val}$ contains $\\textrm{Fib}(n-2)$\n assign(\"n\", reg(\"val\")), // $\\texttt{n}$ now contains $\\textrm{Fib}(n-2)$\n restore(\"val\"), // $\\texttt{val}$ now contains $\\textrm{Fib}(n-1)$\n restore(\"continue\"),\n assign(\"val\", // $\\textrm{Fib}(n-1) + \\textrm{Fib}(n-2)$\n list(op(\"+\"), reg(\"val\"), reg(\"n\"))),\n go_to(reg(\"continue\")), // return to caller, answer in $\\texttt{val}$\n \"immediate_answer\",\n assign(\"val\", reg(\"n\")), // base case: $\\textrm{Fib}(n) = n$\n go_to(reg(\"continue\")),\n \"fib_done\"))\n```\n\n```javascript\nfib_recursive_example\n\tcontroller\n\tmake_machine\n\tstart\n\tfib_recursive_declaration\n\n;\n\nconst fib_recursive_machine =\n make_machine(\n list(\"n\", \"val\", \"continue\"),\n list(list(\"<\", (a, b) => a < b),\n list(\"-\", (a, b) => a - b),\n list(\"+\", (a, b) => a + b)),\n controller_sequence(fib_recursive_controller));\n\nset_register_contents(fib_recursive_machine, \"n\", 6);\nstart(fib_recursive_machine);\nget_register_contents(fib_recursive_machine, \"val\");\n```\n\n```javascript\nfib_recursive_declaration\n\nconst fib_recursive_controller =\n```\n\nController for a machine to compute Fibonacci numbers.", + "token_count": 237, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Designing Register Machines", + "subsection": "Using a Stack to Implement Recursion", + "chunk_index": 6, + "chunk_id": "Computing_with_Register_Machines_Using_a_Stack_to_Implement_Recursion_6" + }, + { + "content": "In section , we will show how to implement a\nJavaScript\nevaluator as a register machine.\n\nIn order to simplify the discussion, we\nwill assume that our register machines can be equipped with a\nlist-structured memory , in which the basic operations for\nmanipulating list-structured data are primitive.\n\nPostulating the existence\nof such a memory is a useful abstraction when one is focusing on the\nmechanisms of control in\nan\ninterpreter, but this does not reflect a realistic view of the actual\nprimitive data operations of contemporary computers.\n\nTo obtain a more\ncomplete picture of how\nsystems can support list-structured memory efficiently,\nwe must investigate how list structure can be represented in a way that is\ncompatible with conventional computer memories.\n\nThere are two considerations in implementing list structure.\n\nThe first is\npurely an issue of representation: how to represent the\nbox-and-pointer structure of\npairs, using only the storage and addressing capabilities of typical computer\nmemories.\n\nThe second issue concerns the management of memory as a\ncomputation proceeds.\n\nThe operation of a\nJavaScript\nsystem depends crucially on the ability to\ncontinually create new data objects.\n\nThese include objects that are\nexplicitly created by the\nJavaScript\nfunctions\nbeing interpreted as well as structures created by the interpreter itself,\nsuch as environments and argument lists.\n\nAlthough the constant creation of\nnew data objects would pose no problem on a computer with an infinite amount\nof rapidly addressable memory, computer memories are available only in\nfinite sizes (more s the pity).\n\nJavaScript\nthus provide an\nautomatic storage allocation facility to\nsupport the illusion of an infinite memory.\n\nWhen a data object is no longer\nneeded, the memory allocated to it is automatically recycled and used to\nconstruct new data objects.\n\nThere are various techniques for providing such\nautomatic storage allocation.", + "token_count": 297, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Storage_Allocation_and_Garbage_Collection_1" + }, + { + "content": "There are various techniques for providing such\nautomatic storage allocation.", + "token_count": 10, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Storage_Allocation_and_Garbage_Collection_2" + }, + { + "content": "The representation method outlined in\nsection solves the problem of\nimplementing list structure, provided that we have an infinite amount of\nmemory.\n\nWith a real computer we will eventually run out of free space in\nwhich to construct new pairs.\n\nHowever, most of the pairs\ngenerated in a typical computation are used only to hold intermediate\nresults.\n\nAfter these\nresults are accessed, the pairs are no longer needed they are\ngarbage.\n\nFor instance, the computation\n\n```javascript\naccumulate((x, y) => x + y,\n 0,\n filter(is_odd, enumerate_interval(0, n)))\n```\n\nconstructs two lists: the enumeration and the result of filtering\nthe enumeration.\n\nWhen the accumulation is complete, these lists are\nno longer needed, and the allocated memory can be reclaimed.\n\nIf we\ncan arrange to collect all the garbage periodically, and if this turns\nout to recycle memory at about the same rate at which we construct new\npairs, we will have preserved the illusion that there is an infinite\namount of memory.\n\nIn order to recycle pairs, we must have a way to determine which\nallocated pairs are not needed (in the sense that their contents can\nno longer influence the future of the computation).\n\nThe method we\nshall examine for accomplishing this is known as garbage\ncollection.\n\nGarbage collection is based on the observation that, at\nany moment in\nan interpretation based on list-structured memory,\nthe only objects that can\naffect the future of the computation are those that can be reached by\nsome succession of\nhead\nand\ntail\noperations starting from the pointers that are currently in the machine\nregisters.\n\nAny memory cell that is not so accessible may be\nrecycled.\n\nThere are many ways to perform garbage collection.\n\nThe method we\nshall examine here is called\nstop-and-copy.", + "token_count": 288, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Maintaining the Illusion of Infinite Memory", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_1" + }, + { + "content": "The method we\nshall examine here is called\nstop-and-copy.\n\nWhen\npair\nconstructs pairs, it allocates these in working memory.\n\nWhen working memory\nis full, we perform garbage collection by locating all the useful pairs in\nworking memory and copying these into consecutive locations in free memory.\n\n(The useful pairs are located by tracing all the\nhead\nand\ntail\npointers, starting with the machine registers.) Since we do not copy the\ngarbage, there will presumably be additional free memory that we can\nuse to allocate new pairs.\n\nIn addition, nothing in the working memory\nis needed, since all the useful pairs in it have been copied.\n\nThus,\nif we interchange the roles of working memory and free memory, we can\ncontinue processing; new pairs will be allocated in the new working\nmemory (which was the old free memory).\n\nWhen this is full, we can\ncopy the useful pairs into the new free memory (which was the old\nworking memory).\n\nWe now use our register-machine language to describe the stop-and-copy\nalgorithm in more detail.\n\nWe will assume that there is a register\ncalled\nthat contains a pointer to a structure\nthat eventually points at all accessible data.\n\nThis can be arranged by\nstoring the contents of all the machine registers in a preallocated list\npointed at by just before starting\ngarbage collection.\n\nWe also assume that, in addition to the current working memory, there is\nfree memory available into which we can copy the useful data.\n\nThe current\nworking memory consists of vectors whose base addresses are in\nregisters called\nthe_heads\nand\nthe_tails,\nand the free memory is in registers called\nnew_heads\nand\nnew_tails.", + "token_count": 272, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Maintaining the Illusion of Infinite Memory", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_2" + }, + { + "content": "The current\nworking memory consists of vectors whose base addresses are in\nregisters called\nthe_heads\nand\nthe_tails,\nand the free memory is in registers called\nnew_heads\nand\nnew_tails.\n\nWhen the garbage-collection\nprocess is complete, the pointer will\npoint into the new memory, all objects accessible from the\nwill have been moved to the new memory,\nand the pointer will indicate the next\nplace in the new memory where a new pair can be allocated.\n\nIn addition,\nthe roles of working memory and new memory will have been\ninterchanged new pairs will be constructed in the new memory,\nbeginning at the place indicated by , and\nthe (previous) working memory will be available as the new memory for the\nnext garbage collection.\n\nFigure\nshows the arrangement of memory just before and just after garbage\ncollection.\n\nReconfiguration of memory by the garbage-collection process.\n\nThe state of the garbage-collection process is controlled by\nmaintaining two pointers:\nand.\n\nThese are initialized to point to the\nbeginning of the new memory.\n\nThe algorithm begins by relocating the pair\npointed at by to the beginning of the new\nmemory.\n\nThe pair is copied, the pointer\nis adjusted to point to the new location, and the\npointer is incremented.\n\nIn addition, the\nold location of the pair is marked to show that its contents have been\nmoved.\n\nThis marking is done as follows: In the\nhead\nposition, we place a special tag that signals that this is an already-moved\nobject.\n\n(Such an object is traditionally called a\nbroken heart.)\nIn the\ntail\nposition we place a\nforwarding address that points at the location to which the object\nhas been moved.\n\nAfter relocating the root, the garbage collector enters its basic\ncycle.", + "token_count": 284, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Maintaining the Illusion of Infinite Memory", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_3" + }, + { + "content": "After relocating the root, the garbage collector enters its basic\ncycle.\n\nThese objects are each\nrelocated, and the pointer is incremented.\n\nTo relocate an object (for example, the object indicated by the\nhead\npointer of the pair we are scanning) we check to see if the object has\nalready been moved (as indicated by the presence of a broken-heart tag\nin the\nhead\nposition of the object).\n\nIf the object has not\nalready been moved, we copy it to the place indicated by\n,\nupdate , set up a broken heart at the\nobject s old location, and update the pointer to the object (in this\nexample, the\nhead\npointer of the pair we are scanning) to point\nto the new location.\n\nIf the object has already been moved, its\nforwarding address (found in the\ntail\nposition of the broken heart) is substituted for the pointer in the pair\nbeing scanned.\n\nEventually, all accessible objects will have been moved and\nscanned, at which point the pointer will\novertake the pointer and the process will\nterminate.\n\nWe can specify the stop-and-copy algorithm as a sequence of instructions for\na register machine.\n\nThe basic step of relocating an object is accomplished\nby a subroutine called\nrelocate_old_result_in_new.\n\nThis subroutine gets its argument, a pointer to the object to be relocated,\nfrom a register named.\n\nIt relocates the designated object\n(incrementing in the process),\nputs a pointer to the relocated object into a register called\n, and returns by branching to the entry\npoint stored in the register\nrelocate_continue.\n\nTo begin garbage collection, we invoke this subroutine to relocate the\npointer, after initializing\nand.\n\nWhen the relocation of has been\naccomplished, we install the new pointer as the new\nand enter the main loop of the garbage\ncollector.", + "token_count": 294, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Maintaining the Illusion of Infinite Memory", + "chunk_index": 4, + "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_4" + }, + { + "content": "When the relocation of has been\naccomplished, we install the new pointer as the new\nand enter the main loop of the garbage\ncollector.\n\nIn the main loop of the garbage collector we must determine whether\nthere are any more objects to be scanned.\n\nWe do this by testing\nwhether the pointer is coincident with\nthe pointer.\n\nIf the pointers are equal,\nthen all accessible objects have been relocated, and we branch to\ngc_flip,\nwhich cleans things up so that we can continue the interrupted computation.\n\nIf there are still pairs to be scanned, we call the relocate subroutine to\nrelocate the\nhead\nof the next pair (by placing the\nhead\npointer in ).\n\nThe\nrelocate_continue\nregister is set up so that the subroutine will return to update the\nhead\npointer.\n\n```javascript\ngc_loop\n\n\"gc_loop\",\n test(list(op(\"===\"), reg(\"scan\"), reg(\"free\"))),\n branch(label(\"gc_flip\")),\n assign(\"old\", list(op(\"vector_ref\"), reg(\"new_heads\"), reg(\"scan\"))),\n assign(\"relocate_continue\", label(\"update_head\")),\n go_to(label(\"relocate_old_result_in_new\")),\n```\n\nAt\nupdate_head,\nwe modify the\nhead\npointer of the pair being scanned, then proceed to relocate the\ntail\nof the pair.\n\nWe return to\nupdate_tail\nwhen that relocation has been accomplished.\n\nAfter relocating and updating\nthe\ntail,\nwe are finished scanning that pair, so we continue with the main loop.\n\n```javascript\nupdate_head\n\n\"update_head\",\n perform(list(op(\"vector_set\"),\n reg(\"new_heads\"), reg(\"scan\"), reg(\"new\"))),\n assign(\"old\", list(op(\"vector_ref\"),\n reg(\"new_tails\"), reg(\"scan\"))),\n assign(\"relocate_continue\", label(\"update_tail\")),\n go_to(label(\"relocate_old_result_in_new\")),\n\n\"update_tail\",\n perform(list(op(\"vector_set\"),\n reg(\"new_tails\"), reg(\"scan\"), reg(\"new\"))),\n assign(\"scan\", list(op(\"+\"), reg(\"scan\"), constant(1))),\n go_to(label(\"gc_loop\")),\n```\n\nThe subroutine\nrelocate_old_result_in_new\nrelocates objects as follows: If the object to be relocated (pointed at by\n) is not a pair, then we return the same\npointer to the object unchanged (in ).\n\n(For example, we may be scanning a pair whose\nhead\nis the number 4.\n\nIf we represent the\nhead\nby , as described in\nsection , then we want the\nrelocated\nhead\npointer to still be.) Otherwise, we\nmust perform the relocation.", + "token_count": 296, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Maintaining the Illusion of Infinite Memory", + "chunk_index": 5, + "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_5" + }, + { + "content": "If we represent the\nhead\nby , as described in\nsection , then we want the\nrelocated\nhead\npointer to still be.) Otherwise, we\nmust perform the relocation.\n\nIf the pointer in\npoints at a yet-unmoved pair, then we move\nthe pair to the first free cell in new memory (pointed at by\n) and set up the broken heart by storing a\nbroken-heart tag and forwarding address at the old location.\n\nThe subroutine relocate_old_result_in_new\nuses a register\noldht\nto hold the\nhead\nor the\ntail\nof the object pointed at by.\n\n```javascript\nrelocate_old_result_in_new\n\n\"relocate_old_result_in_new\",\n test(list(op(\"is_pointer_to_pair\"), reg(\"old\"))),\n branch(label(\"pair\")),\n assign(\"new\", reg(\"old\")),\n go_to(reg(\"relocate_continue\")),\n\"pair\",\n assign(\"oldht\", list(op(\"vector_ref\"),\n reg(\"the_heads\"), reg(\"old\"))),\n test(list(op(\"is_broken_heart\"), reg(\"oldht\"))),\n branch(label(\"already_moved\")),\n assign(\"new\", reg(\"free\")), // new location for pair\n // Update $\\texttt{free}$ pointer\n assign(\"free\", list(op(\"+\"), reg(\"free\"), constant(1))),\n // Copy the head and tail to new memory\n perform(list(op(\"vector_set\"),\n reg(\"new_heads\"), reg(\"new\"),\n reg(\"oldht\"))),\n assign(\"oldht\", list(op(\"vector_ref\"),\n reg(\"the_tails\"), reg(\"old\"))),\n perform(list(op(\"vector_set\"),\n reg(\"new_tails\"), reg(\"new\"),\n reg(\"oldht\"))),\n // Construct the broken heart\n perform(list(op(\"vector_set\"),\n reg(\"the_heads\"), reg(\"old\"),\n constant(\"broken_heart\"))),\n perform(list(op(\"vector_set\"),\n reg(\"the_tails\"), reg(\"old\"),\n reg(\"new\"))),\n go_to(reg(\"relocate_continue\")),\n\"already_moved\",\n assign(\"new\", list(op(\"vector_ref\"),\n reg(\"the_tails\"), reg(\"old\"))),\n go_to(reg(\"relocate_continue\")),\n```\n\nAt the very end of the garbage collection process, we interchange the\nrole of old and new memories by interchanging pointers: interchanging\nthe_heads\nwith\nnew_heads,\nand\nthe_tails\nwith\nnew_tails.\n\nWe will then be ready to perform another garbage\ncollection the next time memory runs out.", + "token_count": 211, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Maintaining the Illusion of Infinite Memory", + "chunk_index": 6, + "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_6" + }, + { + "content": "We will then be ready to perform another garbage\ncollection the next time memory runs out.", + "token_count": 16, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Maintaining the Illusion of Infinite Memory", + "chunk_index": 7, + "chunk_id": "Computing_with_Register_Machines_Maintaining_the_Illusion_of_Infinite_Memory_7" + }, + { + "content": "A conventional computer memory can be thought of as an array of\ncubbyholes, each of which can contain a piece of information.\n\nEach\ncubbyhole has a unique name, called its\naddress or\nlocation.\n\nTypical memory systems provide two primitive operations:\none that fetches the data stored in a specified location and one that\nassigns new data to a specified location.\n\nMemory addresses can be\nincremented to support sequential access to some set of the\ncubbyholes.\n\nMore generally, many important data operations require\nthat memory addresses be treated as data, which can be stored in\nmemory locations and manipulated in machine registers.\n\nThe\nrepresentation of list structure is one application of such\naddress arithmetic.\n\nTo model computer memory, we use a new kind of data structure called a\nvector.\n\nAbstractly, a vector is a compound data object whose\nindividual elements can be accessed by means of an integer index in an\namount of time that is independent of the index.\n\nIn order to describe memory operations, we use two\nfunctions\nfor manipulating vectors:\n-\n-\nvector_ref(vector, n)\nreturns the n th element of the vector.\n-\n-\nvector_set(vector, n, value)\nsets the $n$ th element of the vector to the\ndesignated value.\n\nFor example, if is a vector, then\nvector_ref(v, 5)\ngets the fifth entry in the vector and\nvector_set(v, 5, 7)\nchanges the value of the fifth entry of the vector\nto 7.\n\nFor computer memory, this access can be implemented\nthrough the use of address arithmetic to combine a base address\nthat specifies the beginning location of a vector in memory with an\nindex that specifies the offset of a particular element of the\nvector.\n\nWe can use vectors to implement the basic pair structures required for a\nlist-structured memory.", + "token_count": 291, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Memory as Vectors", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Memory_as_Vectors_1" + }, + { + "content": "We can use vectors to implement the basic pair structures required for a\nlist-structured memory.\n\nWe will represent list structure as follows: A pointer to a pair is an index\ninto the two vectors.\n\nThe\nhead\nof the pair is the entry in\nthe_heads\nwith the designated index, and the\ntail\nof the pair is the entry in\nthe_tails\nwith the designated index.\n\nWe also need a representation for objects other\nthan pairs (such as numbers and\nstrings)\nand a way to distinguish one kind of data from another.\n\nThere are many\nmethods of accomplishing this, but they all reduce to using\ntyped pointers , that is, to extending the notion of\npointer to include information on data type.\n\nThe data type enables the system to\ndistinguish a pointer to a pair (which consists of the pair\ndata type and an index into the memory vectors) from pointers to other\nkinds of data (which consist of some other data type and whatever is\nbeing used to represent data of that type).\n\nTwo data objects are\nconsidered to be the same\n(===)\nif their pointers are identical.\n\nFigure\nillustrates the use of this method to represent\nlist(list(1, 2), 3, 4),\nwhose box-and-pointer diagram is also shown.\n\nWe use letter prefixes to\ndenote the data-type information.\n\nThus, a pointer to the pair with\nindex 5 is denoted , the empty list\nis denoted by the pointer , and a pointer to\nthe number 4 is denoted.\n\nIn the\nbox-and-pointer diagram, we have indicated at the lower left of each pair\nthe vector index that specifies where the\nhead\nand\ntail\nof the pair are stored.\n\nThe blank locations in\nthe_heads\nand\nthe_tails\nmay contain parts of other list structures (not of interest here).", + "token_count": 291, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Memory as Vectors", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Memory_as_Vectors_2" + }, + { + "content": "The blank locations in\nthe_heads\nand\nthe_tails\nmay contain parts of other list structures (not of interest here).\n\nTo deal with numbers that are too large to be represented in the fixed\namount of space allocated for a single pointer, we could use a distinct\nbignum data type, for which the pointer designates a list in which\nthe parts of the number are stored.\n\nA string\nmight be represented as a typed pointer that designates a\nsequence of the characters that form the string s printed\nrepresentation.\n\nThe parser constructs such a sequence\nwhen it encounters a string literal, and the\nstring-concatenation operator + and\nstring-producing\nprimitive functions such as\nstringify\nconstruct such a sequence.\n\nSince we want two instances of a string to\nbe recognized as the same string by\n=== and we want\n===\nto\nbe a simple test for equality of pointers, we must ensure that if the\nsystem sees the same string twice, it will use the same pointer (to\nthe same sequence of characters) to represent both occurrences.\n\nTo\naccomplish this, the system maintains a table, called the\nstring pool ,\nof all the strings it has ever encountered.\n\nWhen the system\nis about to construct a string, it checks the string pool to see if it has ever\nbefore seen the same string.\n\nIf it has not, it\nconstructs a new string (a typed pointer to a new\ncharacter sequence) and enters this pointer in the string pool.\n\nIf the\nsystem has seen the string before, it returns the string pointer\nstored in the string pool.\n\nThis process of replacing strings by unique\npointers is called\nstring interning.\n\nGiven the above representation , we can replace each\nprimitive list operation of a register machine with one or\nmore primitive vector operations.", + "token_count": 297, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Memory as Vectors", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_Memory_as_Vectors_3" + }, + { + "content": "Given the above representation , we can replace each\nprimitive list operation of a register machine with one or\nmore primitive vector operations.\n\nWe also assume that numeric\noperations on pointers (such as incrementing a pointer, using a pair pointer\nto index a vector, or adding two numbers) use only the index portion of\nthe typed pointer.\n\nFor example, we can make a register machine support the instructions\n\n```javascript\nassign(reg$_1$, list(op(\"head\"), reg(reg$_2$)))\n\nassign(reg$_1$, list(op(\"tail\"), reg(reg$_2$)))\n```\n\nif we implement these, respectively, as\n\n```javascript\nassign(reg$_1$, list(op(\"vector_ref\"), reg(\"the_heads\"), reg(reg$_2$)))\n\nassign(reg$_1$, list(op(\"vector_ref\"), reg(\"the_tails\"), reg(reg$_2$)))\n```\n\nThe instructions\n\n```javascript\nperform(list(op(\"set_head\"), reg(reg$_1$), reg(reg$_2$)))\n\nperform(list(op(\"set_tail\"), reg(reg$_1$), reg(reg$_2$)))\n```\n\nare implemented as\n\n```javascript\nperform(list(op(\"vector_set\"), reg(\"the_heads\"), reg(reg$_1$), reg(reg$_2$)))\n\nperform(list(op(\"vector_set\"), reg(\"the_tails\"), reg(reg$_1$), reg(reg$_2$)))\n```\n\nThe operation pair\nis performed by allocating an unused index and storing the arguments to\npair\nin\nthe_heads\nand\nthe_tails\nat that indexed vector position.\n\nWe presume that there is a special\nregister,\n, that always holds a pair pointer\ncontaining the next available index, and that we can increment the index\npart of that pointer to find the next free location.\n\nFor example, the instruction\n\n```javascript\nassign(reg$_1$, list(op(\"pair\"), reg(reg$_2$), reg(reg$_3$)))\n```\n\nis implemented as the following sequence of vector operations:\n\n```javascript\nperform(list(op(\"vector_set\"),\n reg(\"the_heads\"), reg(\"free\"), reg(reg$_2$))),\nperform(list(op(\"vector_set\"),\n reg(\"the_tails\"), reg(\"free\"), reg(reg$_3$))),\nassign(reg$_1$, reg(\"free\")),\nassign(\"free\", list(op(\"+\"), reg(\"free\"), constant(1)))\n```\n\nThe === operation\n\n```javascript\nlist(op(\"===\"), reg(reg$_1$), reg(reg$_2$))\n```\n\nsimply tests the equality of all fields in the registers, and predicates such as is_pair, is_null, is_string, and is_number need only check the type field.\n\nAlthough our register machines use stacks, we need do nothing special\nhere, since stacks can be modeled in terms of lists.\n\nThe stack can be\na list of the saved values, pointed to by a special register\nthe_stack.\n\nThus,\nsave(reg)\ncan be implemented as\n\n```javascript\nassign(\"the_stack\", list(op(\"pair\"), reg(reg), reg(\"the_stack\")))\n```\n\nSimilarly, restore(reg) can be implemented as\n\n```javascript\nassign(reg, list(op(\"head\"), reg(\"the_stack\")))\nassign(\"the_stack\", list(op(\"tail\"), reg(\"the_stack\")))\n```\n\nand perform(list(op(\"initialize_stack\"))) can be implemented as\n\n```javascript\nassign(\"the_stack\", constant(null))\n```", + "token_count": 319, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Memory as Vectors", + "chunk_index": 4, + "chunk_id": "Computing_with_Register_Machines_Memory_as_Vectors_4" + }, + { + "content": "and perform(list(op(\"initialize_stack\"))) can be implemented as\n\nIn conventional computer architectures,\nhowever, it is usually advantageous to allocate the stack as a\nseparate vector.\n\nThen pushing and popping the stack can be\naccomplished by incrementing or decrementing an index into that\nvector.", + "token_count": 41, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "Storage Allocation and Garbage Collection", + "subsection": "Memory as Vectors", + "chunk_index": 5, + "chunk_id": "Computing_with_Register_Machines_Memory_as_Vectors_5" + }, + { + "content": "In section we saw how to\ntransform simple\nJavaScript\nprograms into descriptions of register\nmachines.\n\nWe will now perform this transformation on a more complex\nprogram, the metacircular evaluator of\nsections ,\nwhich shows how the behavior of a\nJavaScript\ninterpreter can be described in terms of the\nfunctions\nand.\n\nThe explicit-control\nevaluator that we develop in this section shows how the underlying\nfunction-calling\nand argument-passing mechanisms used in the\nevaluation process can be described in terms of operations on\nregisters and stacks.\n\nIn addition, the explicit-control evaluator can\nserve as an implementation of a\nJavaScript\ninterpreter, written in a language that is very similar to the native machine\nlanguage of conventional computers.\n\nThe evaluator can be executed by the\nregister-machine simulator of section.\n\nAlternatively, it can be used as a starting point for building a\nmachine-language implementation of a\nJavaScript\nevaluator, or even a\nspecial-purpose machine for evaluating\nJavaScript programs.\n\nFigure shows such a hardware\nimplementation: a silicon chip that acts as an evaluator for\n, the language used in place of JavaScript in the original edition of this book.\n\nThe chip designers started with the data-path and controller specifications\nfor a register machine similar to the evaluator described in this section\nand used design automation programs to construct the\nintegrated-circuit layout.\n\nIn designing the explicit-control evaluator, we must specify the\noperations to be used in our register machine.\n\nWe described the\nmetacircular evaluator in terms of abstract syntax, using\nfunctions\nsuch as\nis_literal\nand\nmake_function.\n\nIn implementing the\nregister machine, we could expand these\nfunctions\ninto sequences of\nelementary list-structure memory operations, and implement these\noperations on our register machine.\n\nHowever, this would make our\nevaluator very long, obscuring the basic structure with\ndetails.", + "token_count": 286, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_The_Explicit-Control_Evaluator_1" + }, + { + "content": "However, this would make our\nevaluator very long, obscuring the basic structure with\ndetails.\n\nIn order to completely specify an evaluator that could be programmed\nin a low-level machine language or implemented in hardware, we would\nreplace these operations by more elementary operations, using the\nlist-structure implementation we described in\nsection.\n\nOur\nJavaScript\nevaluator register machine includes a stack and seven\nregisters:\n,\n,\n,\n,\n,\n, and.\n\nThe comp register\nis used to hold the\ncomponent\nto be evaluated, and contains the environment in\nwhich the evaluation is to be performed.\n\nAt the end of an evaluation,\ncontains the value obtained by evaluating the\ncomponent\nin the designated environment.\n\nThe register is\nused to implement recursion, as explained in section.\n\n(The evaluator needs to call itself recursively, since\nevaluating a component requires evaluating its\nsubcomponents. ) The registers\n,\n, and are\nused in evaluating function applications.\n\nWe will not provide a data-path diagram to show how the registers and\noperations of the evaluator are connected, nor will we give the\ncomplete list of machine operations.\n\nThese are implicit in the\nevaluator s controller, which will be presented in detail.", + "token_count": 192, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_The_Explicit-Control_Evaluator_2" + }, + { + "content": "The body of a block is evaluated with respect to the current environment\nextended by a frame that binds all local names to the value\n\"*unassigned*\".\n\nWe temporarily\nmake use of the val register to\nhold the list of all variables declared in the block, which is obtained\nby\nscan_out_declarations\nfrom section.\n\nThe functions\nscan_@out_@declarations and\nlist_of_unassigned are assumed\nto be available as machine operations.\n\n```javascript\n\"ev_block\",\n assign(\"comp\", list(op(\"block_body\"), reg(\"comp\"))),\n assign(\"val\", list(op(\"scan_out_declarations\"), reg(\"comp\"))),\n\n save(\"comp\"), // so we can use it to temporarily hold $\\texttt{*unassigned*}$ values\n assign(\"comp\", list(op(\"list_of_unassigned\"), reg(\"val\"))),\n assign(\"env\", list(op(\"extend_environment\"),\n reg(\"val\"), reg(\"comp\"), reg(\"env\"))),\n restore(\"comp\"), // the block body\n go_to(label(\"eval_dispatch\")),\n```\n\nAssignments\nare handled by\nev_assignment,\nreached from\neval_@dispatch\nwith the assignment expression in\ncomp.\n\nThe\ncode at\nev_assignment\nfirst evaluates the value part of the expression and then installs the new\nvalue in the environment.\n\nThe function assign_symbol_value\nis assumed to be available as a machine operation.\n\n```javascript\n\"ev_assignment\",\n assign(\"unev\", list(op(\"assignment_symbol\"), reg(\"comp\"))),\n save(\"unev\"), // save variable for later\n assign(\"comp\", list(op(\"assignment_value_expression\"), reg(\"comp\"))),\n save(\"env\"),\n save(\"continue\"),\n assign(\"continue\", label(\"ev_assignment_install\")),\n go_to(label(\"eval_dispatch\")), // evaluate assignment value\n\"ev_assignment_install\",\n restore(\"continue\"),\n restore(\"env\"),\n restore(\"unev\"),\n perform(list(op(\"assign_symbol_value\"),\n reg(\"unev\"), reg(\"val\"), reg(\"env\"))),\n go_to(reg(\"continue\")),\n```\n\nDeclarations of variables and constants are handled in a similar way.\n\nNote that whereas the value of an assignment is the value that was assigned, the value of a declaration is undefined.\n\nThis is handled by setting val to undefined before continuing.\n\nAs in the metacircular evaluator, we transform a function declaration into a constant declaration whose value expression is a lambda expression.\n\nThis happens at ev_function_declaration, which makes the transformation in place in comp and falls through to ev_declaration.\n\n```javascript\n\"ev_function_declaration\",\n assign(\"comp\",\n list(op(\"function_decl_to_constant_decl\"), reg(\"comp\"))),\n\"ev_declaration\",\n assign(\"unev\", list(op(\"declaration_symbol\"), reg(\"comp\"))),\n save(\"unev\"), // save declared name\n assign(\"comp\",\n list(op(\"declaration_value_expression\"), reg(\"comp\"))),\n save(\"env\"),\n save(\"continue\"),\n assign(\"continue\", label(\"ev_declaration_assign\")),\n go_to(label(\"eval_dispatch\")), // evaluate declaration value\n\"ev_declaration_assign\",\n restore(\"continue\"),\n restore(\"env\"),\n restore(\"unev\"),\n perform(list(op(\"assign_symbol_value\"),\n reg(\"unev\"), reg(\"val\"), reg(\"env\"))),\n assign(\"val\", constant(undefined)),\n go_to(reg(\"continue\")),\n```", + "token_count": 298, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "Blocks, Assignments, and Declarations", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Blocks_Assignments_and_Declarations_1" + }, + { + "content": "A function application is specified by a combination containing a function\nexpression and argument expressions.\n\nThe function expression is a subexpression\nwhose value is a function, and the argument expressions are subexpressions whose\nvalues are the arguments to which the function should be applied.\n\nThe metacircular\nevaluate handles applications by calling\nitself recursively to evaluate each element of the combination, and then passing\nthe results to apply , which performs the\nactual function application.\n\nThe explicit-control evaluator does the same thing;\nthese recursive calls are implemented by\ngo_to instructions, together with use of the\nstack to save registers that will be restored after the recursive call returns.\n\nBefore each call we will be\ncareful to identify which registers must be saved\n(because their values will be needed later).\n\nAs in the metacircular evaluator, operator combinations are transformed into\napplications of primitive functions corresponding to the operators.\n\nThis takes\nplace at ev_operator_combination , which\nperforms this transformation in place in comp\nand falls through to\nev_application.\n\nWe begin the evaluation of an application by evaluating the function expression to\nproduce a function, which will later be applied to the evaluated argument\nexpressions.\n\nTo evaluate the function expression, we move it to the\ncomp register and go to\neval_dispatch.\n\nThe environment in the\nenv register is already the correct one in\nwhich to evaluate the function expression.\n\nHowever, we save\nenv because we will need it later to evaluate\nthe argument expressions.\n\nWe also extract the argument expressions into\nunev and save this on the stack.\n\nWe set up\ncontinue so that\neval_dispatch will resume at\nev_appl_did_function_expression after the\nfunction expression has been evaluated.\n\nFirst, however, we save the old value of\ncontinue , which tells the controller where to\ncontinue after the application.", + "token_count": 292, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "Evaluating Function Applications", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Evaluating_Function_Applications_1" + }, + { + "content": "First, however, we save the old value of\ncontinue , which tells the controller where to\ncontinue after the application.\n\nUpon returning from evaluating the function expression, we proceed to evaluate the\nargument expressions of the application and to accumulate the resulting arguments\nin a list, held in argl.\n\n(This is like the\nevaluation of a sequence of statements, except that we collect the values.) First\nwe restore the unevaluated argument expressions and the environment.\n\nWe initialize\nargl to an empty list.\n\nThen we assign to the\nfun register the function that was produced\nby evaluating the function expression.\n\nIf there are no argument expressions, we go\ndirectly to apply_dispatch.\n\nOtherwise we save\nfun on the stack and start the\nargument-evaluation loop:\n\n```javascript\n\"ev_appl_did_function_expression\",\n restore(\"unev\"), // the argument expressions\n restore(\"env\"),\n assign(\"argl\", list(op(\"empty_arglist\"))),\n assign(\"fun\", reg(\"val\")), // the function\n test(list(op(\"is_null\"), reg(\"unev\"))),\n branch(label(\"apply_dispatch\")),\n save(\"fun\"),\n```\n\nEach cycle of the argument-evaluation loop evaluates an argument expression from\nthe list in unev and accumulates the result\ninto argl.\n\nTo evaluate an argument\nexpression, we place it in the comp register\nand go to eval_dispatch , after setting\ncontinue so that execution will resume with\nthe argument-accumulation phase.\n\nBut first we save the arguments accumulated so\nfar (held in argl ), the environment (held in\nenv ), and the remaining argument expressions\nto be evaluated (held in unev ).\n\nA special\ncase is made for the evaluation of the last argument expression, which is handled\nat ev_appl_last_arg.\n\n```javascript\n\"ev_appl_argument_expression_loop\",\n save(\"argl\"),\n assign(\"comp\", list(op(\"head\"), reg(\"unev\"))),\n test(list(op(\"is_last_argument_expression\"), reg(\"unev\"))),\n branch(label(\"ev_appl_last_arg\")),\n save(\"env\"),\n save(\"unev\"),\n assign(\"continue\", label(\"ev_appl_accumulate_arg\")),\n go_to(label(\"eval_dispatch\")),\n```\n\nWhen an argument expression has been evaluated, the value is accumulated into the\nlist held in argl.\n\nThe argument expression is\nthen removed from the list of unevaluated argument expressions in\nunev , and the argument-evaluation loop\ncontinues.", + "token_count": 294, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "Evaluating Function Applications", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Evaluating_Function_Applications_2" + }, + { + "content": "The argument expression is\nthen removed from the list of unevaluated argument expressions in\nunev , and the argument-evaluation loop\ncontinues.\n\nEvaluation of the last argument expression is handled differently, as is the last\nstatement in a sequence.\n\nThere is no need to save the environment or the list of\nunevaluated argument expressions before going to\neval_dispatch , since they will not be\nrequired after the last argument expression is evaluated.\n\nThus, we return from the\nevaluation to a special entry point\nev_appl_accum_last_arg , which restores the\nargument list, accumulates the new argument, restores the saved function, and goes\noff to perform the application.\n\n```javascript\n\"ev_appl_last_arg\",\n assign(\"continue\", label(\"ev_appl_accum_last_arg\")),\n go_to(label(\"eval_dispatch\")),\n\"ev_appl_accum_last_arg\",\n restore(\"argl\"),\n assign(\"argl\", list(op(\"adjoin_arg\"), reg(\"val\"), reg(\"argl\"))),\n restore(\"fun\"),\n go_to(label(\"apply_dispatch\")),\n```\n\nThe details of the argument-evaluation loop determine the\norder in which the\ninterpreter evaluates the argument expressions of a combination (e.g., left to\nright or right to left—see exercise ).\n\nThis order is not determined by the metacircular evaluator, which inherits its\ncontrol structure from the underlying JavaScript in which it is implemented.\n\nBecause we use head in ev_appl_argument_expression_loop\nto extract successive argument expressions from unev\nand tail at\nev_appl_@accumulate_arg to extract the rest of the argument expressions,\nthe explicit-control evaluator will\nevaluate the argument expressions of a combination in left-to-right order,\nas required by the ECMAScript specification.\n\nThe entry point apply_dispatch corresponds to\nthe apply function of the metacircular\nevaluator.\n\nBy the time we get to\napply_dispatch , the\nfun register contains the function to apply\nand argl contains the list of evaluated\narguments to which it must be applied.\n\nThe saved value of\ncontinue (originally passed to\neval_dispatch and saved at\nev_application ), which tells where to return\nwith the result of the function application, is on the stack.", + "token_count": 289, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "Evaluating Function Applications", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_Evaluating_Function_Applications_3" + }, + { + "content": "The saved value of\ncontinue (originally passed to\neval_dispatch and saved at\nev_application ), which tells where to return\nwith the result of the function application, is on the stack.\n\nAs with the metacircular\napply , there are two cases to consider.\n\nEither the function to be applied is a primitive or it is a compound function.\n\n```javascript\n\"apply_dispatch\",\n test(list(op(\"is_primitive_function\"), reg(\"fun\"))),\n branch(label(\"primitive_apply\")),\n test(list(op(\"is_compound_function\"), reg(\"fun\"))),\n branch(label(\"compound_apply\")),\n go_to(label(\"unknown_function_type\")),\n```\n\nWe assume that each\nprimitive is implemented so as to obtain its arguments from\nargl and place its result in\nval.\n\nTo specify how the machine handles\nprimitives, we would have to provide a sequence of controller instructions to\nimplement each primitive and arrange for\nprimitive_apply to dispatch to the\ninstructions for the primitive identified by the contents of\nfun.\n\nSince we are interested in the structure\nof the evaluation process rather than the details of the primitives, we will\ninstead just use an apply_primitive_function\noperation that applies the function in fun to the arguments in\nargl.\n\nFor the purpose of simulating the\nevaluator with the simulator of section we use\nthe function apply_primitive_function , which\ncalls on the underlying JavaScript system to perform the application, just as we\ndid for the metacircular evaluator in section.\n\nAfter computing the value of the primitive\napplication, we restore continue and go to\nthe designated entry point.\n\n```javascript\n\"primitive_apply\",\n assign(\"val\", list(op(\"apply_primitive_function\"),\n reg(\"fun\"), reg(\"argl\"))),\n restore(\"continue\"),\n go_to(reg(\"continue\")),\n```\n\nThe sequence of instructions labeled\ncompound_apply specifies the application of\ncompound functions.\n\nTo apply a compound function, we proceed in a way similar to\nwhat we did in the metacircular evaluator.\n\nWe construct a frame that binds the\nfunction's parameters to the arguments, use this frame to extend the environment\ncarried by the function, and evaluate in this extended environment the body of the\nfunction.", + "token_count": 295, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "Evaluating Function Applications", + "chunk_index": 4, + "chunk_id": "Computing_with_Register_Machines_Evaluating_Function_Applications_4" + }, + { + "content": "We construct a frame that binds the\nfunction's parameters to the arguments, use this frame to extend the environment\ncarried by the function, and evaluate in this extended environment the body of the\nfunction.\n\nWe extract the function's parameters\ninto unev and its environment into\nenv.\n\nWe then replace the environment in\nenv with the environment constructed by\nextending it with bindings of the parameters to the given arguments.\n\nWe then\nextract the body of the function into comp.\n\nThe natural next step would be to restore the saved\ncontinue and proceed to\neval_dispatch to evaluate the body and go to\nthe restored continuation with the result in\nval , as is done for the last statement of a\nsequence.\n\nBut there is a complication!\n\nThe complication has two aspects.\n\nOne is that\nat any point in the evaluation of the body, a\nreturn statement may require the\nfunction to return the value of the return expression as the value of the body.\n\nBut a return statement may be nested arbitrarily deeply in the body; so the stack\nat the moment the return statement is encountered is not necessarily the stack\nthat is needed for a return from the function.\n\nOne way to make it possible to\nadjust the stack for the return is to put a \\emph{marker} on the stack that can be\nfound by the return code.\n\nThis is implemented by the\npush_marker_to_stack instruction.\n\nThe return\ncode can then use the\nrevert_stack_to_marker\ninstruction to restore the stack to the place indicated by the marker before\nevaluating the return expression.\n\nThe other aspect of the complication is that if the evaluation of the body\nterminates without executing a return statement, the value of the body must be\nundefined.", + "token_count": 289, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "Evaluating Function Applications", + "chunk_index": 5, + "chunk_id": "Computing_with_Register_Machines_Evaluating_Function_Applications_5" + }, + { + "content": "The other aspect of the complication is that if the evaluation of the body\nterminates without executing a return statement, the value of the body must be\nundefined.\n\nIf a\nreturn statement is not encountered during evaluation of the body, evaluation of\nthe body will continue at return_undefined.\n\n```javascript\n\"compound_apply\",\n assign(\"unev\", list(op(\"function_parameters\"), reg(\"fun\"))),\n assign(\"env\", list(op(\"function_environment\"), reg(\"fun\"))),\n assign(\"env\", list(op(\"extend_environment\"),\n reg(\"unev\"), reg(\"argl\"), reg(\"env\"))),\n assign(\"comp\", list(op(\"function_body\"), reg(\"fun\"))),\n push_marker_to_stack(),\n assign(\"continue\", label(\"return_undefined\")),\n go_to(label(\"eval_dispatch\")),\n```\n\nThe only places in the interpreter where the\nenv register is assigned a new value are\ncompound_apply and\nev_block (section ).\n\nJust as in the metacircular evaluator,\nthe new environment for evaluation of a function body is constructed from the\nenvironment carried by the function, together with the argument list and the\ncorresponding list of names to be bound.\n\nWhen a return statement is evaluated at\nev_return , we use the\nrevert_stack_@to_marker instruction to restore\nthe stack to its state at the beginning of the function call by removing all\nvalues from the stack down to and including the marker.\n\nAs a consequence,\nrestore(\"continue\") will restore the\ncontinuation of the function call, which was saved at\nev_application.\n\nWe then proceed to evaluate\nthe return expression, whose result will be placed in\nval and thus be the value returned from the\nfunction when we continue after the evaluation of the return expression.\n\n```javascript\n\"ev_return\",\n revert_stack_to_marker(),\n restore(\"continue\"),\n assign(\"comp\", list(op(\"return_expression\"), reg(\"comp\"))),\n go_to(label(\"eval_dispatch\")),\n```\n\nIf no return statement is encountered during evaluation of the function body,\nevaluation continues at return_undefined , the\ncontinuation that was set up at\ncompound_apply.\n\nTo return\nundefined from the function, we put\nundefined into\nval and go to the entry point that was put\nonto the stack at ev_application.\n\nBefore we\ncan restore that continuation from the stack, however, we must remove the marker\nthat was saved at compound_apply.", + "token_count": 299, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "Evaluating Function Applications", + "chunk_index": 6, + "chunk_id": "Computing_with_Register_Machines_Evaluating_Function_Applications_6" + }, + { + "content": "Before we\ncan restore that continuation from the stack, however, we must remove the marker\nthat was saved at compound_apply.\n\nIn chapter we said that the process described by a function such as\n\n```javascript\nfunction sqrt_iter(guess, x) {\n return is_good_enough(guess, x)\n ? guess\n : sqrt_iter(improve(guess, x), x);\n}\n```\n\nis an iterative process.\n\nEven though the\nfunction\nis syntactically recursive (defined in terms of itself), it is not logically\nnecessary for an evaluator to save information in passing from one call to\nsqrt_iter\nto the next.\n\nAn\nevaluator that can execute a\nfunction\nsuch as\nsqrt_iter\nwithout requiring increasing storage as the\nfunction\ncontinues to call itself is called a\ntail-recursive evaluator.\n\nThe metacircular implementation of the evaluator in chapter isn't\ntail-recursive.\n\nIt implements a return statement as a\nconstructor of a return value object containing the value to be\nreturned and inspects the result of a function call to see whether it is\nsuch an object.\n\nIf the evaluation of a function body produces a return value\nobject, the return value of the function is the contents of that object;\notherwise, the return value is\nundefined.\n\nBoth the construction of the\nreturn value object and the eventual inspection of the result of the\nfunction call are deferred operations, which lead to an accumulation of\ninformation on the stack.\n\nOur explicit-control evaluator is tail-recursive, because it does not need to wrap up\nreturn values for inspection and thus avoids the buildup of stack from deferred operations.\n\nAt ev_return , in order to evaluate the expression that\ncomputes the return value of a function, we transfer directly to\neval_dispatch with nothing more on the stack\nthan right before the function call.\n\nWe accomplish this by undoing any saves to\nthe stack by the function (which are useless because we are returning) using\nrevert_stack_to_marker.", + "token_count": 302, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "Evaluating Function Applications", + "chunk_index": 7, + "chunk_id": "Computing_with_Register_Machines_Evaluating_Function_Applications_7" + }, + { + "content": "We accomplish this by undoing any saves to\nthe stack by the function (which are useless because we are returning) using\nrevert_stack_to_marker.\n\nFinally, we transfer to\neval_dispatch without saving any information\non the stack.\n\nThus, when we proceed to evaluate a return expression, the stack is\nthe same as just before the call to the function whose return value we are about\nto compute.\n\nHence, evaluating a return expression—even if it is a function call\n(as in sqrt_iter , where the conditional\nexpression reduces to a call to\nsqrt_iter )—will not cause any information to\naccumulate on the stack.\n\nIf we did not think to take advantage of the fact that it is unnecessary to hold on to the useless information on the stack\n\nwhile evaluating a return expression, we might have taken the straightforward approach of evaluating the return expression, coming back to restore the stack, and finally\n\ncontinuing at the entry point that is waiting for the result of the function call:\n\n```javascript\n\"ev_return\", // alternative implementation: not tail-recursive\n assign(\"comp\", list(op(\"return_expression\"), reg(\"comp\"))),\n assign(\"continue\", label(\"ev_restore_stack\")),\n go_to(label(\"eval_dispatch\")),\n\"ev_restore_stack\",\n revert_stack_to_marker(), // undo saves in current function\n restore(\"continue\"), // undo save at $\\texttt{ev\\char`_application}$\n go_to(reg(\"continue\")),\n```\n\nThis may seem like a minor change to our previous code for evaluation of\nreturn statements :\nThe only difference is that we delay undoing any register saves to the stack until after the evaluation of the return expression.\n\nThe interpreter will still give the same value for any expression.\n\nBut this change\nis fatal to the tail-recursive implementation, because we must now come back after\nevaluating the return expression in order\nto undo the (useless) register saves.\n\nThese extra saves will accumulate during a nest of\nfunction\ncalls.\n\nConsequently, processes such as\nsqrt_iter\nwill require space proportional to the number of iterations rather than requiring\nconstant space.", + "token_count": 301, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "Evaluating Function Applications", + "chunk_index": 8, + "chunk_id": "Computing_with_Register_Machines_Evaluating_Function_Applications_8" + }, + { + "content": "Consequently, processes such as\nsqrt_iter\nwill require space proportional to the number of iterations rather than requiring\nconstant space.\n\nFor example,\nwith tail recursion, an infinite loop can be expressed using only the\nfunction-call and return mechanisms:\n\n```javascript\nfunction count(n) {\n display(n);\n return count(n + 1);\n}\n```\n\nWithout tail recursion, such a function would eventually run out of stack space, and expressing a true iteration would require some control mechanism other than\n\nfunction call.\n\nNote that our JavaScript implementation requires the use of\nreturn in order to be tail-recursive.\n\nBecause the undoing of the register saves takes place at\nev_return , removing\nreturn from the\ncount function above will cause it to\neventually run out of stack space.\n\nThis explains the use of\nreturn in the infinite driver loops in\nchapter.", + "token_count": 132, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "Evaluating Function Applications", + "chunk_index": 9, + "chunk_id": "Computing_with_Register_Machines_Evaluating_Function_Applications_9" + }, + { + "content": "With the implementation of the explicit-control evaluator we come to\nthe end of a development, begun in chapter , in which we have\nexplored successively more precise\nmodels of the evaluation process.\n\nWe started with the relatively informal substitution model, then\nextended this in chapter to the environment model, which enabled us\nto deal with state and change.\n\nIn the metacircular evaluator of\nchapter , we used\nJavaScript\nitself as a language for making more\nexplicit the environment structure constructed during evaluation of an\ncomponent.\n\nNow, with register machines, we have taken a close look\nat the evaluator s mechanisms for storage management,\nargument passing, and control.\n\nAt\neach new level of description, we have had to raise issues and resolve\nambiguities that were not apparent at the previous, less precise\ntreatment of evaluation.\n\nTo understand the behavior of the\nexplicit-control evaluator, we can simulate it and monitor its\nperformance.\n\nWe will install a\ndriver loop in our evaluator machine.\n\nThis plays\nthe role of the\ndriver_loop\nfunction\nof section.\n\nThe evaluator\nwill repeatedly print a prompt, read\na program,\nevaluate\nthe program\nby going to\neval_dispatch,\nand print the result.\n\nIf nothing is entered at the prompt, we jump to the label evaluator_done, which is the last entry point in the controller.\n\nThe following instructions form the beginning of the\nexplicit-control evaluator s controller sequence:\n\n```javascript\n\"read_evaluate_print_loop\",\n perform(list(op(\"initialize_stack\"))),\n assign(\"comp\", list(op(\"user_read\"),\n constant(\"EC-evaluate input:\"))),\n assign(\"comp\", list(op(\"parse\"), reg(\"comp\"))),\n test(list(op(\"is_null\"), reg(\"comp\"))),\n branch(label(\"evaluator_done\")),\n assign(\"env\", list(op(\"get_current_environment\"))),\n assign(\"val\", list(op(\"scan_out_declarations\"), reg(\"comp\"))),\n save(\"comp\"), // so we can use it to temporarily hold $\\texttt{*unassigned*}$ values\n assign(\"comp\", list(op(\"list_of_unassigned\"), reg(\"val\"))),\n assign(\"env\", list(op(\"extend_environment\"),\n reg(\"val\"), reg(\"comp\"), reg(\"env\"))),\n perform(list(op(\"set_current_environment\"), reg(\"env\"))),\n restore(\"comp\"), // the program\n assign(\"continue\", label(\"print_result\")),\n go_to(label(\"eval_dispatch\")),\n\"print_result\",\n perform(list(op(\"user_print\"),\n constant(\"EC-evaluate value:\"), reg(\"val\"))),\n go_to(label(\"read_evaluate_print_loop\")),\n```", + "token_count": 280, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "Running the Evaluator", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_Running_the_Evaluator_1" + }, + { + "content": "The following instructions form the beginning of the\nexplicit-control evaluator s controller sequence:\n\nThe operations get_@current_@environment and set_@current_@environment simply get and set this variable. the_global let current_environment = the_global_environment; function get_current_environment() { return current_environment; } function set_current_environment(env) { current_environment = env; }\n\nWhen we encounter an error in a function (such as the unknown function type error indicated at apply_dispatch), we print an error message and return\n\nto the driver loop.\n\n```javascript\n\"unknown_component_type\",\n assign(\"val\", constant(\"unknown syntax\")),\n go_to(label(\"signal_error\")),\n\n\"unknown_function_type\",\n restore(\"continue\"), // clean up stack (from $\\texttt{apply_dispatch}$)\n assign(\"val\", constant(\"unknown function type\")),\n go_to(label(\"signal_error\")),\n\n\"signal_error\",\n perform(list(op(\"user_print\"),\n constant(\"EC-evaluator error:\"), reg(\"val\"))),\n go_to(label(\"read_evaluate_print_loop\")),\n```\n\nFor the purposes of the simulation, we initialize the stack each time through the driver loop, since it might not be empty after an error\n\n(such as an undeclared name) interrupts an evaluation.\n\nIf we combine all the code fragments presented in sections , we can create an evaluator machine model that we can run using the register-machine\n\nsimulator of section.\n\n```javascript\nconst eceval = make_machine(list(\"comp\", \"env\", \"val\", \"fun\",\n \"argl\", \"continue\", \"unev\"),\n eceval_operations,\n list(\"read_evaluate_print_loop\",\n entire machine controller as given above\n \"evaluator_done\"));\n```\n\n```javascript\neceval\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n user_print\n the_global\n make_machine\n start\n eceval_operations\n eceval_controller\n\nconst eceval =\n make_machine(list(\"comp\", \"env\", \"val\", \"fun\",\n \"argl\", \"continue\", \"unev\"),\n eceval_operations,\n eceval_controller);\n```", + "token_count": 204, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "Running the Evaluator", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_Running_the_Evaluator_2" + }, + { + "content": "simulator of section.\n\nWe must define\nJavaScript functions\nto simulate the operations used as primitives by the evaluator.\n\nThese are\nthe same\nfunctions\nwe used for the metacircular evaluator in\nsection , together with the few additional\nones defined in footnotes throughout section.\n\n```javascript\nconst eceval_operations = list(list(\"is_literal\", is_literal),\n $\\langle\\mathit{complete}\\;\\,\\mathit{list}\\;\\,\\mathit{of}\\;\\mathit{operations}\\:\\,\\mathit{for}\\;\\,\\mathit{eceval}\\;\\,\\mathit{machine}\\rangle$);\n```\n\n```javascript\nprompt_for_input\n\nfunction prompt_for_input(input_prompt) {\n const input = prompt(input_prompt);\n if (is_null(input)) {\n display(\"--- evaluator terminated ---\");\n return null;\n } else {\n display(input_prompt + \"\\n\" + input + \"\\n----------------------------\");\n return parse(input);\n }\n}\n```\n\n```javascript\neceval_operations\n user_read\n empty_arglist\n is_last_argument_expression\n\nconst eceval_operations =\n list(\n // args\n list(\"arg_expressions\" , arg_expressions),\n list(\"function_expression\" , function_expression),\n list(\"is_null\"\n , is_null),\n list(\"head\" , head),\n list(\"is_last_argument_expression\", is_last_argument_expression),\n list(\"tail\" , tail),\n\n //arg\n list(\"empty_arglist\" , empty_arglist),\n list(\"adjoin_arg\" , adjoin_arg),\n\n // comp (sequence)\n list(\"first_statement\" , first_statement),\n list(\"rest_statements\" , rest_statements),\n list(\"is_last_statement\" , is_last_statement),\n list(\"is_empty_sequence\" , is_empty_sequence),\n list(\"sequence_statements\" , sequence_statements),\n\n // eval functions from meta-circular evaluator\n list(\"is_literal\" , is_literal),\n list(\"literal_value\" , literal_value),\n list(\"is_name\" , is_name),\n list(\"symbol_of_name\" , symbol_of_name),\n list(\"is_assignment\" , is_assignment),\n list(\"assignment_symbol\" , assignment_symbol),\n list(\"assignment_value_expression\"\n , assignment_value_expression),\n list(\"assign_symbol_value\" , assign_symbol_value),\n list(\"is_declaration\" , is_declaration),\n list(\"declaration_symbol\" , declaration_symbol),\n list(\"declaration_value_expression\"\n , declaration_value_expression),\n list(\"assign_symbol_value\" , assign_symbol_value),\n list(\"is_lambda_expression\", is_lambda_expression),\n list(\"lambda_parameter_symbols\"\n , lambda_parameter_symbols),\n list(\"lambda_body\" , lambda_body),\n list(\"is_return_statement\" , is_return_statement),\n list(\"return_expression\" , return_expression),\n list(\"is_conditional\"\n , is_conditional),\n list(\"conditional_predicate\"\n\t\t\t\t , conditional_predicate),\n list(\"conditional_consequent\"\n , conditional_consequent),\n list(\"conditional_alternative\"\n , conditional_alternative),\n list(\"is_sequence\" , is_sequence),\n list(\"is_block\" , is_block),\n list(\"block_body\" , block_body),\n list(\"scan_out_declarations\"\n , scan_out_declarations),\n list(\"list_of_unassigned\" , list_of_unassigned),\n list(\"is_application\" , is_application),\n list(\"is_primitive_function\"\n , is_primitive_function),\n list(\"apply_primitive_function\"\n , apply_primitive_function),\n list(\"is_compound_function\", is_compound_function),\n list(\"function_parameters\" , function_parameters),\n list(\"function_environment\", function_environment),\n list(\"function_body\" , function_body),\n list(\"extend_environment\" , extend_environment),\n list(\"make_function\" , make_function),\n\n list(\"lookup_symbol_value\" , lookup_symbol_value),\n\n list(\"get_current_environment\"\n , get_current_environment),\n list(\"set_current_environment\"\n , set_current_environment),\n\n // Unsorted\n list(\"is_function_declaration\" , is_function_declaration),\n list(\"function_declaration_body\" , function_declaration_body),\n list(\"function_declaration_parameters\" , function_declaration_parameters),\n list(\"function_declaration_name\" , function_declaration_name),\n list(\"function_decl_to_constant_decl\", function_decl_to_constant_decl),\n list(\"declaration_symbol\" , declaration_symbol),\n list(\"is_operator_combination\", is_operator_combination),\n list(\"operator_combination_to_application\", operator_combination_to_application),\n list(\"parse\", parse),\n\n // generic helpers\n list(\"is_truthy\", is_truthy),\n list(\"is_falsy\", v => !is_truthy(v)),\n list(\"is_null\", is_null),\n\n list(\"user_read\", user_read),\n list(\"user_print\", user_print),\n list(\"display\", display),\n list(\"list\", list)\n );\n```", + "token_count": 300, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "Running the Evaluator", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_Running_the_Evaluator_3" + }, + { + "content": "These are\nthe same\nfunctions\nwe used for the metacircular evaluator in\nsection , together with the few additional\nones defined in footnotes throughout section.\n\n```javascript\nec_eval_all\n eceval\n user_read\n type_function\n\nconst test_program_1 = \" \\\nfunction test1() { \\\n display('A 1'); \\\n const result = test2(); \\\n return display(stringify(result) + ' 3'); \\\n display('I should not be printed 7'); \\\n} \\\nfunction test2() { \\\n display('B 2'); \\\n return 'C'; \\\n display('I should not be printed 4'); \\\n} \\\nfunction test3() { \\\n display('Hello from test3'); \\\n} \\\nfunction test4() { \\\n return display('Hello from test4'); \\\n} \\\ndisplay('5 ' + stringify(test1())); \\\ndisplay('6 ' + stringify(test3())); // Should print undefined \\\ndisplay('8 ' + stringify(test4())); \\\n\";\n\nconst test_program_2 = \" \\\nfunction adder(a, b) { \\\n return \\\n a === 0 \\\n ? b \\\n : adder(a - 1, b + 1); \\\n 42; \\\n} \\\ndisplay(\\\"7 + 5 = \\\" + stringify(adder(7, 5)); \\\n\";\n\nstart(eceval);\nget_register_contents(eceval, \"val\");\n\nconst the_global_environment = setup_environment();\nstart(eceval);\n```\n\n```javascript\nEC-evaluate input:\n\nfunction append(x, y) {\n return is_null(x)\n ? y\n : pair(head(x), append(tail(x), y));\n}\n\nEC-evaluate value:\nundefined\n```\n\n```javascript\nEC-evaluate input:\n\nappend(list(\"a\", \"b\", \"c\"), list(\"d\", \"e\", \"f\"));\n\nEC-evaluate value:\n[\"a\", [\"b\", [\"c\", [\"d\", [\"e\", [\"f\", null]]]]]]\n```\n\nOf course, evaluating\nprograms\nin this way will take much longer\nthan if we had directly typed them into\nJavaScript,\nbecause of the\nmultiple levels of simulation involved.\n\nOur\nprograms\nare evaluated\nby the explicit-control-evaluator machine, which is being simulated by\na\nJavaScript\nprogram, which is itself being evaluated by the\nJavaScript\ninterpreter.\n\nSimulation can be a powerful tool to guide the implementation of\nevaluators.\n\nSimulations make it easy not only to explore variations\nof the register-machine design but also to monitor the performance of\nthe simulated evaluator.", + "token_count": 299, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "Running the Evaluator", + "chunk_index": 4, + "chunk_id": "Computing_with_Register_Machines_Running_the_Evaluator_4" + }, + { + "content": "Simulations make it easy not only to explore variations\nof the register-machine design but also to monitor the performance of\nthe simulated evaluator.\n\nWe can\nobserve the number of stack operations required to evaluate various\nprograms\nby defining the evaluator register machine with the\nversion of the simulator that collects statistics on stack use\n(section ), and adding an instruction at the\nevaluator s\nprint_result\nentry point to print the statistics:\n\n```javascript\n\"print_result\",\n perform(list(op(\"print_stack_statistics\"))), // added instruction\n // rest is same as before\n perform(list(op(\"user_print\"),\n constant(\"EC-evaluate value:\"), reg(\"val\"))),\n go_to(label(\"read_evaluate_print_loop\")),\n```\n\nInteractions with the evaluator now look like this:\n\n```javascript\nEC-evaluate input:\n\nfunction factorial (n) {\n return n === 1\n ? 1\n : factorial(n - 1) * n;\n}\n\ntotal pushes = 4\nmaximum depth = 3\nEC-evaluate value:\nundefined\n```\n\n```javascript\nEC-evaluate input:\n\nfactorial(5);\n\ntotal pushes = 151\nmaximum depth = 28\nEC-evaluate value:\n120\n```\n\nNote that the driver loop of the evaluator reinitializes the stack at the start of each interaction, so that the statistics printed will refer only\n\nto stack operations used to evaluate the previous program.", + "token_count": 179, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "Running the Evaluator", + "chunk_index": 5, + "chunk_id": "Computing_with_Register_Machines_Running_the_Evaluator_5" + }, + { + "content": "The central element in the evaluator is the sequence of instructions beginning at\neval_dispatch.\n\nThis corresponds to the\nevaluate\nfunction\nof the metacircular evaluator described in section.\n\nWhen the controller starts at\neval_dispatch,\nit evaluates the\ncomponent\nspecified by\nin the environment specified by.\n\nWhen evaluation is\ncomplete, the controller will go to the entry point stored in\n, and the\nregister will hold the value of the\ncomponent.\n\nAs with the metacircular\nevaluate,\nthe structure of\neval_dispatch\nis a case analysis on the syntactic type of the\ncomponent\nto be evaluated.\n\n```javascript\n\"eval_dispatch\",\n test(list(op(\"is_literal\"), reg(\"comp\"))),\n branch(label(\"ev_literal\")),\n test(list(op(\"is_name\"), reg(\"comp\"))),\n branch(label(\"ev_name\")),\n test(list(op(\"is_application\"), reg(\"comp\"))),\n branch(label(\"ev_application\")),\n test(list(op(\"is_operator_combination\"), reg(\"comp\"))),\n branch(label(\"ev_operator_combination\")),\n test(list(op(\"is_conditional\"), reg(\"comp\"))),\n branch(label(\"ev_conditional\")),\n test(list(op(\"is_lambda_expression\"), reg(\"comp\"))),\n branch(label(\"ev_lambda\")),\n test(list(op(\"is_sequence\"), reg(\"comp\"))),\n branch(label(\"ev_sequence\")),\n test(list(op(\"is_block\"), reg(\"comp\"))),\n branch(label(\"ev_block\")),\n test(list(op(\"is_return_statement\"), reg(\"comp\"))),\n branch(label(\"ev_return\")),\n test(list(op(\"is_function_declaration\"), reg(\"comp\"))),\n branch(label(\"ev_function_declaration\")),\n test(list(op(\"is_declaration\"), reg(\"comp\"))),\n branch(label(\"ev_declaration\")),\n test(list(op(\"is_assignment\"), reg(\"comp\"))),\n branch(label(\"ev_assignment\")),\n go_to(label(\"unknown_component_type\")),\n```\n\nNumbers and strings ,\nnames,\nand\nlambda\nexpressions have no subexpressions to be evaluated.\n\nFor these, the evaluator simply\nplaces the correct value in the register and\ncontinues execution at the entry point specified by.\n\nEvaluation of simple expressions is performed\nby the following controller code:\n\n```javascript\n\"ev_literal\",\n assign(\"val\", list(op(\"literal_value\"), reg(\"comp\"))),\n go_to(reg(\"continue\")),\n\n\"ev_name\",\n assign(\"val\", list(op(\"symbol_of_name\"), reg(\"comp\"), reg(\"env\"))),\n assign(\"val\", list(op(\"lookup_symbol_value\"),\n reg(\"val\"), reg(\"env\"))),\n go_to(reg(\"continue\")),\n\n\"ev_lambda\",\n assign(\"unev\", list(op(\"lambda_parameter_symbols\"), reg(\"comp\"))),\n assign(\"comp\", list(op(\"lambda_body\"), reg(\"comp\"))),\n assign(\"val\", list(op(\"make_function\"),\n reg(\"unev\"), reg(\"comp\"), reg(\"env\"))),\n go_to(reg(\"continue\")),\n```\n\nObserve how ev_lambda uses the unev and comp registers to hold the parameters and body of the lambda expression so that they can be passed\n\nto the make_function operation, along with the environment in.\n\nAs with the metacircular evaluator, syntactic forms are handled by selectively\nevaluating fragments of the component.\n\nFor a\nconditional, we must evaluate the\npredicate and decide, based on the value of predicate, whether to evaluate the\nconsequent or the alternative.", + "token_count": 282, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "The Dispatcher and Basic Evaluation", + "chunk_index": 1, + "chunk_id": "Computing_with_Register_Machines_The_Dispatcher_and_Basic_Evaluation_1" + }, + { + "content": "For a\nconditional, we must evaluate the\npredicate and decide, based on the value of predicate, whether to evaluate the\nconsequent or the alternative.\n\nTo evaluate the predicate expression, we move it to\nthe comp register and go to\neval_dispatch.\n\nThe environment in the\nenv register is already the correct one in\nwhich to evaluate the predicate.\n\nHowever, we save\nenv because we will need it later to\nevaluate the consequent or the alternative.\n\nWe set up\ncontinue so that evaluation will resume at\nev_conditional_decide after the predicate\nhas been evaluated.\n\nFirst, however, we save the old value of\ncontinue , which we will need later in order\nto return to the evaluation of the statement that is waiting for the value of\nthe conditional.\n\n```javascript\n\"ev_conditional\",\n save(\"comp\"), // save conditional for later\n save(\"env\"),\n save(\"continue\"),\n assign(\"continue\", label(\"ev_conditional_decide\")),\n assign(\"comp\", list(op(\"conditional_predicate\"), reg(\"comp\"))),\n go_to(label(\"eval_dispatch\")), // evaluate the predicate\n```\n\nWhen we resume at ev_conditional_decide after\nevaluating the predicate, we test whether it was true or false\nand, depending on the result, place either the consequent or the alternative in\ncomp before going to\neval_dispatch.\n\nNotice that restoring\nenv and\ncontinue here sets up\neval_dispatch to have the correct environment\nand to continue at the right place to receive the value of the conditional.\n\n```javascript\n\"ev_conditional_decide\",\n restore(\"continue\"),\n restore(\"env\"),\n restore(\"comp\"),\n test(list(op(\"is_falsy\"), reg(\"val\"))),\n branch(label(\"ev_conditional_alternative\")),\n\"ev_conditional_consequent\",\n assign(\"comp\", list(op(\"conditional_consequent\"), reg(\"comp\"))),\n go_to(label(\"eval_dispatch\")),\n\"ev_conditional_alternative\",\n assign(\"comp\", list(op(\"conditional_alternative\"), reg(\"comp\"))),\n go_to(label(\"eval_dispatch\")),\n```\n\nThe portion of the explicit-control evaluator beginning at ev_sequence , which handles sequences of statements, is analogous to the metacircular evaluator's eval_@sequence function.\n\nThe entries at ev_sequence_next and\nev_sequence_continue form a loop that\nsuccessively evaluates each statement in a sequence.\n\nThe list of unevaluated\nstatements is kept in unev.\n\nAt ev_sequence we place the sequence of\nstatements to be evaluated in unev.", + "token_count": 291, + "has_code": true, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "The Dispatcher and Basic Evaluation", + "chunk_index": 2, + "chunk_id": "Computing_with_Register_Machines_The_Dispatcher_and_Basic_Evaluation_2" + }, + { + "content": "At ev_sequence we place the sequence of\nstatements to be evaluated in unev.\n\nOtherwise we start the\nsequence-evaluation loop, first saving the value of continue on the stack, because\nthe continue register will be used for local flow of control in the loop, and the original\nvalue is needed for continuing after the statement sequence.\n\nBefore evaluating\neach statement, we check to see if there are additional statements to be evaluated\nin the sequence.\n\nIf so, we save the rest of the unevaluated statements (held in\nunev ) and the environment in which these must\nbe evaluated (held in env ) and call\neval_dispatch to evaluate the statement,\nwhich has been placed in comp.\n\nThe\ntwo saved registers are restored after this evaluation, at\nev_sequence_continue.\n\nThe final statement in the sequence is handled differently, at the entry point\nev_sequence_last_statement.\n\nSince there are\nno more statements to be evaluated after this one, we need not save\nunev or\nenv before going to\neval_dispatch.\n\nThe value of the whole\nsequence is the value of the last statement, so after the evaluation of the last\nstatement there is nothing left to do except continue at the entry point that was\nsaved at ev_sequence.\n\nRather than setting up continue to arrange\nfor eval_dispatch to return here and then\nrestoring continue from the stack and\ncontinuing at that entry point, we restore\ncontinue from the stack before going to\neval_dispatch , so that\neval_dispatch will continue at that entry\npoint after evaluating the statement.\n\nUnlike eval_sequence in the metacircular\nevaluator, ev_sequence does not need to check whether a return statement was\nevaluated so as to terminate the sequence evaluation.\n\nThe explicit\ncontrol in this evaluator allows a return statement to jump directly to\nthe continuation of the current function application without resuming the\nsequence evaluation.", + "token_count": 300, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "The Dispatcher and Basic Evaluation", + "chunk_index": 3, + "chunk_id": "Computing_with_Register_Machines_The_Dispatcher_and_Basic_Evaluation_3" + }, + { + "content": "The explicit\ncontrol in this evaluator allows a return statement to jump directly to\nthe continuation of the current function application without resuming the\nsequence evaluation.\n\nBecause a return statement jumps out of the sequence-evaluation code,\nthe restores of saved registers at ev_sequence_continue\nwon t be executed.\n\nWe will see later how the return statement removes these values from the stack.", + "token_count": 61, + "has_code": false, + "chapter": "Computing with Register Machines", + "section": "The Explicit-Control Evaluator", + "subsection": "The Dispatcher and Basic Evaluation", + "chunk_index": 4, + "chunk_id": "Computing_with_Register_Machines_The_Dispatcher_and_Basic_Evaluation_4" + }, + { + "content": "In our study of program design, we have seen that expert programmers\ncontrol the complexity of their designs with the same general\ntechniques used by designers of all complex systems.\n\nThey combine\nprimitive elements to form compound objects, they abstract compound\nobjects to form higher-level building blocks, and they preserve\nmodularity by adopting appropriate large-scale views of system\nstructure.\n\nIn illustrating these techniques, we have used\nJavaScript\nas a language for describing processes and for constructing computational\ndata objects and processes to model complex phenomena in the real world.\n\nHowever, as we confront increasingly complex problems, we will find that\nJavaScript,\nor indeed any fixed programming language, is not sufficient for our needs.\n\nWe must constantly turn to new languages in order to express our ideas more\neffectively.\n\nEstablishing new languages is a powerful strategy for\ncontrolling complexity in engineering design; we can often enhance our\nability to deal with a complex problem by adopting a new language that\nenables us to describe (and hence to think about) the problem in a different\nway, using primitives, means of combination, and means of abstraction that\nare particularly well suited to the problem at hand.\n\nProgramming is endowed with a multitude of languages.\n\nThere are\nphysical languages, such as the\nmachine languages for particular\ncomputers.\n\nThese languages are concerned with the representation of\ndata and control in terms of individual bits of storage and primitive\nmachine instructions.\n\nThe machine-language programmer is concerned\nwith using the given hardware to erect systems and utilities for the\nefficient implementation of resource-limited computations.\n\nHigh-level\nlanguages, erected on a machine-language substrate, hide concerns\nabout the representation of data as collections of bits and the\nrepresentation of programs as sequences of primitive instructions.", + "token_count": 285, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": null, + "subsection": null, + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Metalinguistic_Abstraction_1" + }, + { + "content": "High-level\nlanguages, erected on a machine-language substrate, hide concerns\nabout the representation of data as collections of bits and the\nrepresentation of programs as sequences of primitive instructions.\n\nMetalinguistic abstraction establishing\nnew languages plays an important role in all branches of engineering\ndesign.\n\nIt is particularly important to computer programming, because\nin programming not only can we formulate new languages but we can also\nimplement these languages by constructing evaluators.\n\nAn\nevaluator (or interpreter ) for a programming language is a\nfunction\nthat, when applied to\na statement or expression\nof the language, performs the actions required to evaluate that\nstatement or\nexpression.\n\nIt is no exaggeration to regard this as the most fundamental idea in\nprogramming:\nThe evaluator, which determines the meaning of\nstatements and\nexpressions in a programming language, is just another program.\n\nTo appreciate this point is to change our images of ourselves as\nprogrammers.\n\nWe come to see ourselves as designers of languages,\nrather than only users of languages designed by others.\n\nIn fact, we can regard almost any program as the evaluator for some\nlanguage.\n\nFor instance, the polynomial manipulation system of\nsection embodies the rules of\npolynomial arithmetic and implements them in terms of operations on\nlist-structured data.\n\nIf we augment this system with\nfunctions\nto read and print polynomial expressions, we have the core of a\nspecial-purpose language for dealing with problems in symbolic mathematics.\n\nThe digital-logic simulator of\nsection and the constraint\npropagator of section are legitimate\nlanguages in their own right, each with its own primitives, means of\ncombination, and means of abstraction.", + "token_count": 262, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": null, + "subsection": null, + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Metalinguistic_Abstraction_2" + }, + { + "content": "The digital-logic simulator of\nsection and the constraint\npropagator of section are legitimate\nlanguages in their own right, each with its own primitives, means of\ncombination, and means of abstraction.\n\nWe now embark on a tour of the technology by which languages are\nestablished in terms of other languages.\n\nIn this chapter we shall use\nJavaScript\nas a base, implementing evaluators as\nJavaScript\nfunctions.\n\nWe will take the first step in understanding how languages are implemented\nby building an evaluator for\nJavaScript\nitself.\n\nThe language implemented by our evaluator will be a subset of\nJavaScript.\n\nAlthough the evaluator described in this chapter is written for a\nparticular\nsubset of JavaScript,\nit contains the essential structure of an evaluator for any\nlanguage\ndesigned for writing programs for a sequential machine.\n\n(In fact, most\nlanguage processors contain, deep within them, a little\nevaluator.)\nThe evaluator has been simplified for the purposes of illustration and\ndiscussion, and some features have been left out that would be\nimportant to include in a production-quality\nJavaScript\nsystem.\n\nNevertheless, this simple evaluator is adequate to execute most of\nthe programs in this book.\n\nAn important advantage of making the evaluator accessible as a\nJavaScript\nprogram is that we can implement alternative evaluation rules by describing\nthese as modifications to the evaluator program.\n\nOne place where we can use\nthis power to good effect is to gain extra control over the ways in which\ncomputational models embody the notion of time, which was so central to the\ndiscussion in chapter.\n\nThere, we mitigated some of the complexities\nof state and assignment by using streams to decouple the representation of\ntime in the world from time in the computer.\n\nOur stream programs, however,\nwere sometimes cumbersome, because they were constrained by the\napplicative-order evaluation of\nJavaScript.", + "token_count": 297, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": null, + "subsection": null, + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_Metalinguistic_Abstraction_3" + }, + { + "content": "Our stream programs, however,\nwere sometimes cumbersome, because they were constrained by the\napplicative-order evaluation of\nJavaScript.\n\nSection implements a\nmore ambitious linguistic change, whereby statements and expressions\nhave many values, rather than just a single value.\n\nIn this language of\nnondeterministic computing , it is natural to express processes that\ngenerate all possible values for statements and expressions and then search\nfor those values that satisfy certain constraints.\n\nIn terms of models of\ncomputation and time, this is like having time branch into a set of\npossible futures and then searching for appropriate time\nlines.\n\nWith our nondeterministic evaluator, keeping track of multiple values\nand performing searches are handled automatically by the underlying\nmechanism of the language.\n\nIn section we implement a\nlogic-programming language in which knowledge is expressed in terms\nof relations, rather than in terms of computations with inputs and outputs.\n\nEven though this makes the language drastically different from\nJavaScript,\nor indeed from any conventional language, we will see that\nthe logic-programming evaluator shares the essential structure of the\nJavaScript\nevaluator.", + "token_count": 175, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": null, + "subsection": null, + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_Metalinguistic_Abstraction_4" + }, + { + "content": "Now that we have an evaluator expressed as a\nJavaScript\nprogram, we can experiment with alternative choices in\nlanguage design\nsimply by modifying the evaluator.\n\nIndeed, new languages are often\ninvented by first writing an evaluator that embeds the new language\nwithin an existing high-level language.\n\nFor example, if we wish to\ndiscuss some aspect of a proposed modification to\nJavaScript\nwith another member of the\nJavaScript\ncommunity, we can supply an evaluator that embodies\nthe change.\n\nThe recipient can then experiment with the new\nevaluator and send back comments as further modifications.\n\nNot only\ndoes the high-level implementation base make it easier to test and\ndebug the evaluator; in addition, the embedding enables the designer\nto snarf features\nfrom the underlying language, just as our embedded\nJavaScript\nevaluator uses primitives and control structure from the underlying\nJavaScript.\n\nOnly later (if ever) need the designer go to the trouble of building a\ncomplete implementation in a low-level language or in hardware.\n\nIn\nthis section and the next we explore some variations on\nJavaScript\nthat provide significant additional expressive power.", + "token_count": 179, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Lazy_Evaluation_1" + }, + { + "content": "In this section we will implement a normal-order language that is\nthe same as\nJavaScript\nexcept that compound\nfunctions\nare non-strict in each argument.\n\nPrimitive\nfunctions\nwill still be strict.\n\nIt is not difficult to modify the evaluator of\nsection so that the language it\ninterprets behaves this way.\n\nAlmost all the required changes center around\nfunction\napplication.\n\nThe basic idea is that, when applying a\nfunction,\nthe interpreter must determine which arguments are to be evaluated and which\nare to be delayed.\n\nThe delayed arguments are not evaluated; instead, they\nare transformed into objects called\nthunk s.\n\nThe thunk must contain the information required to produce the value\nof the argument when it is needed, as if it had been evaluated at\nthe time of the application.\n\nThus, the thunk must contain the\nargument expression and the environment in\nwhich the\nfunction\napplication is being evaluated.\n\nThe process of evaluating the expression in a thunk is called\nforcing.\n\nIn general, a thunk will be forced only when its value is needed:\nwhen it is passed to a primitive\nfunction\nthat will use the value of the thunk; when it is the value of a predicate of\na conditional; and when it is the value of\na function expression\nthat is about to be\napplied as a\nfunction.\n\nOne design choice we have available is whether or not to\nmemoize thunks, similar to the optimization for streams in\nsection.\n\nWith memoization, the first\ntime a thunk is forced, it stores the value that is computed.\n\nSubsequent\nforcings simply return the stored value without repeating the computation.\n\nWe ll make our interpreter memoize, because this is more efficient for\nmany applications.\n\nThere are tricky considerations here,\nhowever.", + "token_count": 286, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "An Interpreter with Lazy Evaluation", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_An_Interpreter_with_Lazy_Evaluation_1" + }, + { + "content": "There are tricky considerations here,\nhowever.\n\nThe is_application clause of becomes\n\n```javascript\neval_lazy_example\n functions_4_1_1_lazy\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_program = parse(\"1; { true; 3; }\");\nevaluate(my_program, the_empty_environment);\n```\n\n```javascript\neval_lazy\n eval_lazy_example\n 3\n\n: is_application(component)\n? apply(actual_value(function_expression(component), env),\n arg_expressions(component), env)\n\nfunction evaluate(component, env) {\n return is_literal(component)\n ? literal_value(component)\n : is_name(component)\n ? lookup_symbol_value(symbol_of_name(component), env)\n : is_application(component)\n ? apply(actual_value(function_expression(component), env),\n arg_expressions(component), env)\n : is_operator_combination(component)\n ? evaluate(operator_combination_to_application(component), env)\n : is_conditional(component)\n ? eval_conditional(component, env)\n : is_lambda_expression(component)\n ? make_function(lambda_parameter_symbols(component),\n lambda_body(component), env)\n : is_sequence(component)\n ? eval_sequence(sequence_statements(component), env)\n : is_block(component)\n ? eval_block(component, env)\n : is_return_statement(component)\n ? eval_return_statement(component, env)\n : is_function_declaration(component)\n ? evaluate(function_decl_to_constant_decl(component), env)\n : is_declaration(component)\n ? eval_declaration(component, env)\n : is_assignment(component)\n ? eval_assignment(component, env)\n : error(component, \"unknown syntax -- evaluate\");\n}\n```\n\nThis is almost the same as the\nis_application\nclause of\nevaluate\nin section.\n\nFor lazy evaluation,\nhowever, we call with the\nargument\nexpressions, rather than the arguments produced by evaluating them.\n\nSince\nwe will need the environment to construct thunks if the arguments are to be\ndelayed, we must pass this as well.\n\nWe still evaluate the\nfunction expression,\nbecause\nneeds the actual\nfunction\nto be applied in order to dispatch on its type (primitive versus compound)\nand apply it.\n\nWhenever we need the actual value of an expression, we use\n\n```javascript\nactual_value_lazy_example\n functions_4_1_1_lazy\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nactual_value(parse(\"1 + 2;\"), the_global_environment);\n```\n\n```javascript\nactual_value_lazy\n actual_value_lazy_example\n 3\n\nfunction actual_value(exp, env) {\n return force_it(evaluate(exp, env));\n}\n```\n\ninstead of just evaluate, so that if the expression s value is a thunk, it will be forced.\n\nOur new version of is also almost the\nsame as the version in section.\n\nThe difference is that\nevaluate\nhas passed in unevaluated\nargument\nexpressions: For primitive\nfunctions\n(which are strict), we evaluate all the arguments before applying the\nprimitive; for compound\nfunctions\n(which are non-strict) we delay all the\narguments before applying the\nfunction.", + "token_count": 301, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "An Interpreter with Lazy Evaluation", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_An_Interpreter_with_Lazy_Evaluation_2" + }, + { + "content": "The difference is that\nevaluate\nhas passed in unevaluated\nargument\nexpressions: For primitive\nfunctions\n(which are strict), we evaluate all the arguments before applying the\nprimitive; for compound\nfunctions\n(which are non-strict) we delay all the\narguments before applying the\nfunction.\n\n```javascript\napply_lazy\n list_of_arg_values\n apply_lazy_example\n 3\n\nfunction apply(fun, args, env) {\n if (is_primitive_function(fun)) {\n return apply_primitive_function(\n fun,\n list_of_arg_values(args, env)); // changed\n } else if (is_compound_function(fun)) {\n const result = evaluate(\n function_body(fun),\n extend_environment(\n function_parameters(fun),\n list_of_delayed_args(args, env), // changed\n function_environment(fun)));\n return is_return_value(result)\n ? return_value_content(result)\n : undefined;\n } else {\n error(fun, \"unknown function type -- apply\");\n }\n}\n```\n\nThe functions that process the arguments are just like list_of_values from section , except that list_of_delayed_args delays the arguments instead of evaluating them, and list_of_arg_values\n\nuses actual_value instead of evaluate:\n\n```javascript\nlist_of_arg_values\n\nfunction list_of_arg_values(exps, env) {\n return map(exp => actual_value(exp, env), exps);\n}\nfunction list_of_delayed_args(exps, env) {\n return map(exp => delay_it(exp, env), exps);\n}\n```\n\nThe other place we must change the evaluator is in the handling of conditionals, where we must use actual_value instead of to get the value\n\nof the predicate expression before testing whether it is true or false:\n\n```javascript\neval_if_lazy_example\n functions_4_1_1_lazy\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_cond_expr = parse(\"true ? 1 : 2;\");\neval_conditional(my_cond_expr, the_empty_environment);\n```\n\n```javascript\neval_if_lazy\n eval_if_lazy_example\n 1\n\nfunction eval_conditional(component, env) {\n return is_truthy(actual_value(conditional_predicate(component), env))\n ? evaluate(conditional_consequent(component), env)\n : evaluate(conditional_alternative(component), env);\n}\n```\n\nFinally, we must change the\ndriver_loop\nfunction\n(from section ) to use\nactual_@value\ninstead of\n,\nso that if a delayed value is propagated back to the\nread-evaluate-print loop,\nit will be forced before being printed.\n\nWe also change the prompts to indicate that\nthis is the lazy evaluator:", + "token_count": 273, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "An Interpreter with Lazy Evaluation", + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_An_Interpreter_with_Lazy_Evaluation_3" + }, + { + "content": "We also change the prompts to indicate that\nthis is the lazy evaluator:\n\nWith these changes made, we can start the evaluator and test it.\n\nThe\nsuccessful evaluation of the\ntry_me\nexpression\ndiscussed in section indicates\nthat the interpreter is performing lazy evaluation:\n\n```javascript\ndriver_loop_lazy_example\n driver_loop_lazy\n\nconst the_global_environment = setup_environment();\ndriver_loop(the_global_environment);\n\ndriver_loop(the_global_environment);\n\n// L-evaluate input:\n// function try_me(a, b) { return a === 0 ? 1 : b; }\n// L-evaluate value:\n// undefined\n\n// L-evaluate input:\n// try_me(0, head(null));\n// L-evaluate value:\n// 1\n```\n\n```javascript\nL-evaluate input:\n\nfunction try_me(a, b) {\n return a === 0 ? 1 : b;\n}\n\nL-evaluate value:\nundefined\n```\n\n```javascript\nL-evaluate input:\n\n try_me(0, head(null));\n\nL-evaluate value:\n1\n```\n\nOur evaluator must arrange to create thunks when\nfunctions\nare applied to arguments and to force these thunks later.\n\nA thunk must\npackage an expression together with the environment, so that the argument\ncan be produced later.\n\nTo force the thunk, we simply extract the expression\nand environment from the thunk and evaluate the expression in the\nenvironment.\n\nWe use\nactual_value\nrather than\nso that in case the value of the expression is itself a thunk, we will force\nthat, and so on, until we reach something that is not a thunk:\n\n```javascript\nforce_it_lazy_v1\n\nfunction force_it(obj) {\n return is_thunk(obj)\n ? actual_value(thunk_exp(obj), thunk_env(obj))\n : obj;\n}\n```\n\nOne easy way to package an expression with an environment is to make a list\ncontaining the expression and the environment.\n\nThus, we create a thunk as\nfollows:\n\n```javascript\ndelay_it_lazy\n eval_lazy_example\n\nfunction delay_it(exp, env) {\n return list(\"thunk\", exp, env);\n}\nfunction is_thunk(obj) {\n return is_tagged_list(obj, \"thunk\");\n}\nfunction thunk_exp(thunk) { return head(tail(thunk)); }\n\nfunction thunk_env(thunk) { return head(tail(tail(thunk))); }\n```\n\nActually, what we want for our interpreter is not quite this, but\nrather thunks that have been memoized.", + "token_count": 299, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "An Interpreter with Lazy Evaluation", + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_An_Interpreter_with_Lazy_Evaluation_4" + }, + { + "content": "Actually, what we want for our interpreter is not quite this, but\nrather thunks that have been memoized.\n\n```javascript\nforce_it_lazy\n eval_lazy_example\n\nfunction is_evaluated_thunk(obj) {\n return is_tagged_list(obj, \"evaluated_thunk\");\n}\nfunction thunk_value(evaluated_thunk) {\n return head(tail(evaluated_thunk));\n}\n\nfunction force_it(obj) {\n if (is_thunk(obj)) {\n const result = actual_value(thunk_exp(obj), thunk_env(obj));\n set_head(obj, \"evaluated_thunk\");\n set_head(tail(obj), result); // replace exp with its value\n set_tail(tail(obj), null); // forget unneeded env\n return result;\n } else if (is_evaluated_thunk(obj)) {\n return thunk_value(obj);\n } else {\n return obj;\n }\n}\n```\n\nNotice that the same delay_it function works both with and without memoization.", + "token_count": 90, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "An Interpreter with Lazy Evaluation", + "chunk_index": 5, + "chunk_id": "Metalinguistic_Abstraction_An_Interpreter_with_Lazy_Evaluation_5" + }, + { + "content": "In section , where we began\nour discussion of models of evaluation, we noted that\nJavaScript\nis an applicative-order language, namely, that all the arguments to\nJavaScript\nfunctions\nare evaluated when the\nfunction\nis applied.\n\nIn contrast, normal-order languages delay evaluation of\nfunction\narguments until the actual argument values are needed.\n\nDelaying evaluation of\nfunction\narguments until the last possible moment (e.g., until they are required by a\nprimitive operation) is called\nlazy evaluation.\n\nConsider the\nfunction\n\n```javascript\ntry_me_example\n\ntry_me(0, head(null));\n```\n\n```javascript\ntry_me\n try_me_example\n 1\n\nfunction try_me(a, b) {\n return a === 0 ? 1 : b;\n}\n\n// Source Academy opens this program\n// in lazy mode. Choose \"Source §2\" to\n// to compare with strict mode\nfunction try_me(a, b) {\n return a === 0 ? 1 : b;\n}\n```\n\nEvaluating\ntry_me(0, head(null)); signals\nan error in\nJavaScript.\n\nWith lazy evaluation, there would be no error.\n\nEvaluating the\nstatement\nwould return 1, because the argument\nhead(null)\nwould never be evaluated.\n\nAn example that exploits lazy evaluation is the declaration of a function\n\n```javascript\nunless\n unless_example\n\nfunction unless(condition, usual_value, exceptional_value) {\n return condition ? exceptional_value : usual_value;\n}\n\n// Source Academy opens this program\n// in lazy mode. Choose \"Source §2\" to\n// to compare with strict mode\nfunction unless(condition, usual_value, exceptional_value) {\n return condition ? exceptional_value : usual_value;\n}\n```\n\nthat can be used in statements such as\n\n```javascript\nxs_is_null\n\nconst xs = null;\n```\n\n```javascript\nunless_example\n unless\n xs_is_null\n\nunless(is_null(xs), head(xs), display(\"error: xs should not be null\"));\n```\n\nThis won t work in an applicative-order language because both the\nusual value and the exceptional value will be evaluated before\nis called (compare\nexercise ).\n\nAn advantage of lazy evaluation is\nthat some\nfunctions,\nsuch as , can do useful computation\neven if evaluation of some of their arguments would produce errors or\nwould not terminate.", + "token_count": 308, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "Normal Order and Applicative Order", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Normal_Order_and_Applicative_Order_1" + }, + { + "content": "An advantage of lazy evaluation is\nthat some\nfunctions,\nsuch as , can do useful computation\neven if evaluation of some of their arguments would produce errors or\nwould not terminate.\n\nIf the argument is evaluated before\nthe body of the\nfunction\nis entered we say that the\nfunction\nis\nstrict in that\nargument.\n\nIn a purely applicative-order language, all\nfunctions\nare strict in each argument.\n\nIn a purely normal-order language, all compound\nfunctions\nare non-strict in each argument, and primitive\nfunctions\nmay be either strict or non-strict.\n\nThere are also languages (see\nexercise ) that give\nprogrammers detailed control over the strictness of the\nfunctions\nthey define.\n\nA striking example of a\nfunction\nthat can usefully be made non-strict is\npair\n(or, in general, almost any constructor for data structures).\n\nOne can do useful computation, combining elements to form\ndata structures and operating on the resulting data structures,\neven if the values of the elements are not known.\n\nIt makes perfect\nsense, for instance, to compute the length of a list without knowing\nthe values of the individual elements in the list.\n\nWe will exploit\nthis idea in section to implement the\nstreams of chapter as lists formed of non-strict\npairs.", + "token_count": 202, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "Normal Order and Applicative Order", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Normal_Order_and_Applicative_Order_2" + }, + { + "content": "In section , we showed how to\nimplement streams as delayed lists.\n\nWe used a lambda expression\nto construct a\npromise to compute the\ntail\nof a stream, without actually fulfilling that promise until later.\n\nWe were forced to create streams as a new kind of data object similar but not identical to lists, and this required us to reimplement many ordinary list operations (, , and so on) for use with streams.\n\nWith lazy evaluation, streams and lists can be identical, so there is\nno need for\nseparate list and stream operations.\n\nAll we need to do is to arrange matters\nso that\npair\nis non-strict.\n\nOne way to accomplish this is to extend the lazy evaluator\nto allow for non-strict primitives, and to implement\npair\nas one of these.\n\nAn easier way is to recall\n(section ) that there is no fundamental need\nto implement\npair\nas a primitive at all.\n\nInstead, we can represent\npairs as\nfunctions :\n\n```javascript\npair_lazy_header\n\nconst my_pair_lazy = `\n```\n\n```javascript\npair_lazy_footer\n\nhead(tail(pair(1, pair(3, 2))));\n`;\n```\n\n```javascript\npair_lazy_example\n parse_and_evaluate_lazy\n pair_lazy_header\n pair_lazy\n pair_lazy_footer\n 3\n\nparse_and_evaluate(my_pair_lazy);\n```\n\n```javascript\npair_lazy\n\nfunction pair(x, y) {\n return m => m(x, y);\n}\nfunction head(z) {\n return z((p, q) => p);\n}\nfunction tail(z) {\n return z((p, q) => q);\n}\n```\n\nIn terms of these basic operations, the standard definitions of the list\noperations will work with infinite lists (streams) as well as finite ones,\nand the stream operations can be implemented as list operations.\n\nHere are\nsome examples:\n\n```javascript\nlist_lib_test_header\n\nconst my_list_lib_test = `\n```\n\n```javascript\nlist_lib_test_footer\n\nlist_ref(integers, 17);\n`;\n```\n\n```javascript\nlist_lib_test\n parse_and_evaluate_lazy\n list_lib_test_header\n pair_lazy\n list_library_lazy\n list_lib_test_header\n list_lib_test_footer\n 18\n\nparse_and_evaluate(my_list_lib_test);\n```", + "token_count": 277, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "Streams as Lazy Lists", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Streams_as_Lazy_Lists_1" + }, + { + "content": "Here are\nsome examples:\n\n```javascript\nL-evaluate input:\n\nlist_ref(integers, 17);\n\nL-evaluate value:\n18\n```\n\nNote that these lazy lists are even lazier than the streams of\nchapter : The\nhead\nof the list, as well as the\ntail,\nis delayed.\n\nIn fact, even accessing the\nhead\nor\ntail\nof a lazy pair need not force the value of a list element.\n\nThe value will be\nforced only when it is really needed e.g., for use as the argument\nof a primitive, or to be printed as an answer.\n\nLazy pairs also help with the problem that arose with streams in\nsection , where we\nfound that formulating stream models of systems with loops may require us to\nsprinkle our programs with\nadditional lambda expressions for delays, beyond the ones required to construct a stream pair.\n\nWith lazy evaluation, all arguments to\nfunctions\nare delayed uniformly.\n\nFor instance, we can implement\nfunctions\nto integrate lists and solve differential equations as we originally\nintended in section :\n\n```javascript\nlazy_integral_header\n\nconst my_integral = `\n```\n\n```javascript\nlazy_integral_footer\n\nlist_ref(solve(x => x, 1, 0.1), 10);\n`;\n```\n\n```javascript\nlazy_integral_test\n parse_and_evaluate_lazy\n lazy_integral_header\n pair_lazy\n list_library_lazy\n lazy_integral\n lazy_integral_footer\n 2.5937424601\n\nparse_and_evaluate(my_integral);\n```\n\n```javascript\nlazy_integral\n\nfunction integral(integrand, initial_value, dt) {\n const int = pair(initial_value,\n add_lists(scale_list(integrand, dt),\n int));\n return int;\n}\nfunction solve(f, y0, dt) {\n const y = integral(dy, y0, dt);\n const dy = map(f, y);\n return y;\n}\n```\n\n```javascript\nL-evaluate input:\n\nlist_ref(solve(x => x, 1, 0.001), 1000);\n\nL-evaluate value:\n2.716924\n```", + "token_count": 242, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Lazy Evaluation", + "subsection": "Streams as Lazy Lists", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Streams_as_Lazy_Lists_2" + }, + { + "content": "In chapter we stressed that computer science deals with\nimperative\n(how to) knowledge, whereas mathematics deals with declarative (what\nis) knowledge.\n\nIndeed, programming languages require that the\nprogrammer express knowledge in a form that indicates the step-by-step\nmethods for solving particular problems.\n\nOn the other hand,\nhigh-level languages provide, as part of the language implementation,\na substantial amount of methodological knowledge that frees\nthe user from concern with numerous details of how a specified\ncomputation will progress.\n\nMost programming languages, including\nJavaScript,\nare organized around\ncomputing the values of mathematical functions.\n\nExpression-oriented\nlanguages\n(such as , C, Python, and JavaScript)\ncapitalize on the\npun that an expression that describes the value of a\nfunction may also be interpreted as a means of computing that value.\n\nBecause of this, most programming languages are strongly biased toward\nunidirectional computations (computations with well-defined inputs and\noutputs).\n\nThere are, however, radically different programming languages\nthat relax this bias.\n\nWe saw one such example in\nsection , where the objects of\ncomputation were arithmetic constraints.\n\nIn a constraint system the\ndirection and the order of computation are not so well specified; in\ncarrying out a computation the system must therefore provide more detailed\nhow to knowledge than would be the case with an ordinary\narithmetic computation.\n\nThis does not mean, however, that the user is\nreleased altogether from the responsibility of providing imperative\nknowledge.\n\nThere are many constraint networks that implement the same set\nof constraints, and the user must choose from the set of mathematically\nequivalent networks a suitable network to specify a particular computation.\n\nThe nondeterministic program evaluator of\nsection also moves\naway from the view that programming is about constructing algorithms for\ncomputing unidirectional functions.", + "token_count": 283, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": null, + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Logic_Programming_1" + }, + { + "content": "The nondeterministic program evaluator of\nsection also moves\naway from the view that programming is about constructing algorithms for\ncomputing unidirectional functions.\n\nLogic\nprogramming extends this idea by combining a relational vision of programming\nwith a powerful kind of symbolic pattern matching called\nunification.\n\nThis approach, when it works, can be a very\npowerful way to write programs.\n\nPart of the power comes from the fact that a single what is\nfact can be used to solve a number of different problems that would have\ndifferent how to components.\n\nAs an example, consider the\noperation, which takes two lists as\narguments and combines their elements to form a single list.\n\nIn a procedural\nlanguage such as\nJavaScript,\nwe could define in terms of the\nbasic list constructor\npair,\nas we did in section :\n\n```javascript\nfunction append(x, y) {\n return is_null(x)\n ? y\n : pair(head(x), append(tail(x), y));\n}\n```\n\nThis\nfunction\ncan be regarded as a translation into\nJavaScript\nof the following two rules, the first of which covers the case where the\nfirst list is empty and the second of which handles the case of a nonempty\nlist, which is a\npair\nof two parts:\n-\n-\nFor any list , the empty list and\nto\nform.\n-\n-\nFor any , ,\n, and ,\npair(u, v)\nand\nto form\npair(u, z)\nif and\nto form.\n\nUsing the\nfunction,\nwe can answer questions such as\nFind the of\nlist(\"a\", \"b\")\nand\nlist(\"c\", \"d\").\n\nBut the same two rules are also sufficient for answering the following\nsorts of questions, which the\nfunction\ncan t answer:\nFind a list\nthat\ns with\nlist(\"a\", \"b\")\nto produce\nlist(\"a\", \"b\", \"c\", \"d\").\n\nFind all and\nthat to form\nlist(\"a\", \"b\", \"c\", \"d\").", + "token_count": 290, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": null, + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Logic_Programming_2" + }, + { + "content": "Find all and\nthat to form\nlist(\"a\", \"b\", \"c\", \"d\").\n\nHow to knowledge is provided automatically by the\ninterpreter to allow this single pair of rules to be used to answer all\nthree types of questions about.\n\nContemporary logic programming languages (including the one we\nimplement here) have substantial deficiencies, in that their general\nhow to methods can lead them into spurious infinite loops or\nother undesirable behavior.\n\nLogic programming is an active field of research\nin computer science.\n\nEarlier in this chapter we explored the technology of implementing\ninterpreters and described the elements that are essential to an\ninterpreter for a\nJavaScript-like\nlanguage (indeed, to an interpreter for any conventional language).\n\nNow we\nwill apply these ideas to discuss an interpreter for a logic programming\nlanguage.\n\nWe call this\nlanguage the\nquery language , because it is very useful for\nretrieving information from data bases by formulating\nqueries , or questions, expressed in the language.\n\nEven though the\nquery language is very different from\nJavaScript,\nwe will find it convenient to describe the language in terms of the same\ngeneral framework we have been using all along: as a collection of primitive\nelements, together with means of combination that enable us to combine\nsimple elements to create more complex elements and means of abstraction\nthat enable us to regard complex elements as single conceptual units.\n\nAn\ninterpreter for a logic programming language is considerably more complex\nthan an interpreter for a language like\nJavaScript.\n\nNevertheless, we will see\nthat our\nquery-language interpreter contains many of the same elements\nfound in the interpreter of section.", + "token_count": 265, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": null, + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_Logic_Programming_3" + }, + { + "content": "Nevertheless, we will see\nthat our\nquery-language interpreter contains many of the same elements\nfound in the interpreter of section.\n\nAlso, a central role\nis played in the implementation by a frame data structure, which determines\nthe correspondence between symbols and their associated values.\n\nOne\nadditional interesting aspect of our query-language implementation is\nthat we make substantial use of streams, which were introduced in\nchapter.", + "token_count": 65, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": null, + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_Logic_Programming_4" + }, + { + "content": "Logic programming excels in providing interfaces to\ndata bases for\ninformation retrieval.\n\nThe query language we shall implement in this\nchapter is designed to be used in this way.\n\nIn order to illustrate what the query system does, we will show how it\ncan be used to manage the data base of personnel records for\nGargle, a thriving high-technology company in the\nBoston area.\n\nThe language provides pattern-directed access to\npersonnel information and can also take advantage of general rules in\norder to make logical deductions.\n\nThe personnel data base for Gargle\ncontains\nassertions about company personnel.\n\nHere is the\ninformation about Ben Bitdiddle, the resident computer wizard:\n\n```javascript\nsample_data_base_1\n process_query\n sample_data_base_1_example\n\naddress(list(\"Bitdiddle\", \"Ben\"),\n list(\"Slumerville\", list(\"Ridge\", \"Road\"), 10))\njob(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))\nsalary(list(\"Bitdiddle\", \"Ben\"), 122000)\n\nprocess_query(`assert(address(list(\"Bitdiddle\", \"Ben\"),\n list(\"Slumerville\", list(\"Ridge\", \"Road\"), 10)))`);\nprocess_query(`assert(job(list(\"Bitdiddle\", \"Ben\"),\n list(\"computer\", \"wizard\")))`);\nprocess_query(`assert(salary(list(\"Bitdiddle\", \"Ben\"), 122000))`);\n```\n\n```javascript\nsample_data_base_1_example\n sample_data_base_1\n process_query\n 'salary(list(\"Bitdiddle\", \"Ben\"), 122000)'\n\nprocess_query('salary(list(\"Bitdiddle\", \"Ben\"), $x)');\n\nfirst_answer('salary(list(\"Bitdiddle\", \"Ben\"), $x)');\n```\n\nAssertions look just like function applications in JavaScript, but they actually represent information in the data base.\n\nThe first symbolshere address, job and salarydescribe the kind of information contained in the respective assertion, and the arguments are lists or primitive values such as strings and numbers.\n\nThe first symbols do not need to be declared, as do constants or variables in JavaScript; their scope is global.\n\nAs resident wizard, Ben is in charge of the company s computer\ndivision, and he supervises two programmers and one technician.\n\nHere\nis the information about them:", + "token_count": 246, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Deductive Information Retrieval", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Deductive_Information_Retrieval_1" + }, + { + "content": "Here\nis the information about them:\n\n```javascript\nsample_data_base_2_example\n\nprocess_query('supervisor($x, list(\"Bitdiddle\", \"Ben\"))');\n\nfirst_answer('supervisor($x, list(\"Bitdiddle\", \"Ben\"))');\n```\n\n\\newpage\\noindent There is also a programmer trainee, who is supervised by Alyssa:\n\n```javascript\nsample_data_base_3\n process_query\n sample_data_base_2\n 'supervisor(list(\"Reasoner\", \"Louis\"), list(\"Hacker\", \"Alyssa\", \"P\"))'\n sample_data_base_3_example\n\naddress(list(\"Reasoner\", \"Louis\"),\n list(\"Slumerville\", list(\"Pine\", \"Tree\", \"Road\"), 80))\njob(list(\"Reasoner\", \"Louis\"),\n list(\"computer\", \"programmer\", \"trainee\"))\nsalary(list(\"Reasoner\", \"Louis\"), 62000)\nsupervisor(list(\"Reasoner\", \"Louis\"), list(\"Hacker\", \"Alyssa\", \"P\"))\n\nprocess_query('assert(address(list(\"Reasoner\", \"Louis\"), list(\"Slumerville\", list(\"Pine\", \"Tree\", \"Road\"), 80)))');\nprocess_query('assert(job(list(\"Reasoner\", \"Louis\"), list(\"computer\", \"programmer\", \"trainee\")))');\nprocess_query('assert(salary(list(\"Reasoner\", \"Louis\"), 62000))');\nprocess_query('assert(supervisor(list(\"Reasoner\", \"Louis\"), list(\"Hacker\", \"Alyssa\", \"P\")))');\n```\n\n```javascript\nsample_data_base_3_example\n\nprocess_query('supervisor(list(\"Reasoner\", $x), $y)');\n\nfirst_answer('supervisor(list(\"Reasoner\", $x), $y)');\n```\n\nAll these people are in the computer division, as indicated by the word \"computer\" as the first item in their job descriptions.\n\nBen is a high-level employee.\n\nHis supervisor is the company s big\nwheel himself:\n\n```javascript\nsample_data_base_4\n process_query\n sample_data_base_3\n 'address(list(\"Warbucks\", \"Oliver\"), list(\"Swellesley\", list(\"Top\", \"Heap\", \"Road\")))'\n sample_data_base_4_example\n\nsupervisor(list(\"Bitdiddle\", \"Ben\"), list(\"Warbucks\", \"Oliver\"))\n\naddress(list(\"Warbucks\", \"Oliver\"),\n list(\"Swellesley\", list(\"Top\", \"Heap\", \"Road\")))\njob(list(\"Warbucks\", \"Oliver\"), list(\"administration\", \"big\", \"wheel\"))\nsalary(list(\"Warbucks\", \"Oliver\"), 314159)\n\nprocess_query('assert(supervisor(list(\"Bitdiddle\", \"Ben\"), list(\"Warbucks\", \"Oliver\")))');\n\nprocess_query('assert(address(list(\"Warbucks\", \"Oliver\"), list(\"Swellesley\", list(\"Top\", \"Heap\", \"Road\"))))');\nprocess_query('assert(job(list(\"Warbucks\", \"Oliver\"), list(\"administration\", \"big\", \"wheel\")))');\nprocess_query('assert(salary(list(\"Warbucks\", \"Oliver\"), 314159))');\n```\n\n```javascript\nsample_data_base_4_example\n\nprocess_query('address($x, list(\"Swellesley\", $y))');\n\nfirst_answer('address($x, list(\"Swellesley\", $y))');\n```\n\nBesides the computer division supervised by Ben, the company has an accounting division, consisting of a chief accountant and his assistant:\n\n```javascript\nsample_data_base_5\n process_query\n sample_data_base_4\n 'job(list(\"Cratchit\", \"Robert\"), list(\"accounting\", \"scrivener\"))'\n sample_data_base_5_example\n\naddress(list(\"Scrooge\", \"Eben\"),\n list(\"Weston\", list(\"Shady\", \"Lane\"), 10))\njob(list(\"Scrooge\", \"Eben\"), list(\"accounting\", \"chief\", \"accountant\"))\nsalary(list(\"Scrooge\", \"Eben\"), 141421)\nsupervisor(list(\"Scrooge\", \"Eben\"), list(\"Warbucks\", \"Oliver\"))\n\naddress(list(\"Cratchit\", \"Robert\"),\n list(\"Allston\", list(\"N\", \"Harvard\", \"Street\"), 16))\njob(list(\"Cratchit\", \"Robert\"), list(\"accounting\", \"scrivener\"))\nsalary(list(\"Cratchit\", \"Robert\"), 26100)\nsupervisor(list(\"Cratchit\", \"Robert\"), list(\"Scrooge\", \"Eben\"))\n\nprocess_query('assert(address(list(\"Scrooge\", \"Eben\"), list(\"Weston\", list(\"Shady\", \"Lane\"), 10)))');\nprocess_query('assert(job(list(\"Scrooge\", \"Eben\"), list(\"accounting\", \"chief\", \"accountant\")))');\nprocess_query('assert(salary(list(\"Scrooge\", \"Eben\"), 141421))');\nprocess_query('assert(supervisor(list(\"Scrooge\", \"Eben\"), list(\"Warbucks\", \"Oliver\")))');\n\nprocess_query('assert(address(list(\"Cratchit\", \"Robert\"), list(\"Allston\", list(\"N\", \"Harvard\", \"Street\"), 16)))');\nprocess_query('assert(job(list(\"Cratchit\", \"Robert\"), list(\"accounting\", \"scrivener\")))');\nprocess_query('assert(salary(list(\"Cratchit\", \"Robert\"), 26100))');\nprocess_query('assert(supervisor(list(\"Cratchit\", \"Robert\"), list(\"Scrooge\", \"Eben\")))');\n```\n\n```javascript\nsample_data_base_5_example\n\nprocess_query('job($x, pair(\"accounting\", $y))');\n\nfirst_answer('job($x, pair(\"accounting\", $y))');\n```\n\nThere is also an administrative assistant for the big wheel:", + "token_count": 302, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Deductive Information Retrieval", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Deductive_Information_Retrieval_2" + }, + { + "content": "There is also an administrative assistant for the big wheel:\n\n```javascript\nsample_data_base_6_example\n\nprocess_query('address($x, list(y, list(\"Onion\", \"Square\"), $n))');\n\nfirst_answer('address($x, list(y, list(\"Onion\", \"Square\"), $n))');\n```\n\nThe data base also contains assertions about which kinds of jobs can\nbe done by people holding other kinds of jobs.\n\nFor instance, a\ncomputer wizard can do the jobs of both a computer programmer and a\ncomputer technician:\n\n```javascript\nsample_data_base_7\n process_query\n sample_data_base_6\n 'can_do_job(list(\"computer\", \"wizard\"), list(\"computer\", \"technician\"))'\n sample_data_base_7_example\n\ncan_do_job(list(\"computer\", \"wizard\"),\n list(\"computer\", \"programmer\"))\ncan_do_job(list(\"computer\", \"wizard\"),\n list(\"computer\", \"technician\"))\n\nprocess_query('assert(can_do_job(list(\"computer\", \"wizard\"), list(\"computer\", \"programmer\")))');\nprocess_query('assert(can_do_job(list(\"computer\", \"wizard\"), list(\"computer\", \"technician\")))');\n```\n\n```javascript\nsample_data_base_7_example\n\nprocess_query('can_do_job($x, list(\"computer\", $y))');\n\nfirst_answer('can_do_job($x, list(\"computer\", $y))');\n```\n\nA computer programmer could fill in for a trainee:\n\n```javascript\nsample_data_base_8\n process_query\n sample_data_base_7\n 'can_do_job(list(\"computer\", \"programmer\"), list(\"computer\", \"programmer\", \"trainee\"))'\n sample_data_base_8_example\n\ncan_do_job(list(\"computer\", \"programmer\"),\n list(\"computer\", \"programmer\", \"trainee\"))\n\nprocess_query('assert(can_do_job(list(\"computer\", \"programmer\"), list(\"computer\", \"programmer\", \"trainee\")))');\n```\n\n```javascript\nsample_data_base_8_example\n\nprocess_query('can_do_job($x, pair(\"computer\", $y))');\n\nfirst_answer('can_do_job($x, pair(\"computer\", $y))');\n```\n\nAlso, as is well known,\n\n```javascript\nsample_data_base_9\n process_query\n sample_data_base_8\n 'can_do_job(list(\"administration\", \"assistant\"), list(\"administration\", \"big\", \"wheel\"))'\n sample_data_base_9_example\n\ncan_do_job(list(\"administration\", \"assistant\"),\n list(\"administration\", \"big\", \"wheel\"))\n\nprocess_query('assert(can_do_job(list(\"administration\", \"assistant\"), list(\"administration\", \"big\", \"wheel\")))');\n```\n\n```javascript\nsample_data_base_9_example\n\nprocess_query('can_do_job(list($x, \"assistant\"), $y)');\n\nfirst_answer('can_do_job(list($x, \"assistant\"), $y)');\n```\n\nThe query language allows users to retrieve information from the data\nbase by posing queries in response to the system s prompt.\n\nFor example, to find all computer programmers one can say\n\n```javascript\nsimple_queries_input\n```\n\n```javascript\nsimple_queries_1\n process_query\n sample_data_base_9\n query_driver_loop\n 'job(list(\"Fect\", \"Cy\", \"D\"), list(\"computer\", \"programmer\"))'\n simple_queries_1_example\n\nQuery input:\n\njob($x, list(\"computer\", \"programmer\"))\n```\n\n```javascript\nsimple_queries_1_example\n\nfirst_answer('job($x, list(\"computer\", \"programmer\"))');\n\nprocess_query('job($x, list(\"computer\", \"programmer\"))');\n\n// Query input:\n// job($x, list(\"computer\", \"programmer\"))\n// Query results:\n// job(list(\"Hacker\", \"Alyssa\", \"P\"), list(\"computer\", \"programmer\"))\n// job(list(\"Fect\", \"Cy\", \"D\"), list(\"computer\", \"programmer\"))\n```\n\nThe system will respond with the following items:\n\n```javascript\nQuery results:\njob(list(\"Hacker\", \"Alyssa\", \"P\"), list(\"computer\", \"programmer\"))\njob(list(\"Fect\", \"Cy\", \"D\"), list(\"computer\", \"programmer\"))\n```\n\nThe input query specifies that we are looking for entries in the data\nbase that match a certain\npattern.\n\nIn this example, the pattern specifies as the kind of information that we are looking for.", + "token_count": 311, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Deductive Information Retrieval", + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_Deductive_Information_Retrieval_3" + }, + { + "content": "In this example, the pattern specifies as the kind of information that we are looking for.\n\nThe anything that can be the first item in the matching assertion is specified by a pattern variable, $x.\n\nAs pattern variables, we use JavaScript names that start with a dollar sign.\n\nWe will see below why it is useful to specify names for pattern variables rather than just putting a single symbol such as into patterns to represent anything.\n\nThe system responds to a simple query by showing all entries in the data\nbase that match the specified pattern.\n\nA pattern can have more than one variable.\n\nFor example, the query\n\n```javascript\nsimple_queries_2\n process_query\n sample_data_base_9\n query_driver_loop\n 'address(list(\"Aull\", \"DeWitt\"), list(\"Slumerville\", list(\"Onion\", \"Square\"), 5))'\n simple_queries_2_example\n\naddress($x, $y)\n```\n\n```javascript\nsimple_queries_2_example\n\nfirst_answer('address($x, $y)');\n\nprocess_query('address($x, $y)');\n\n// query input:\n// address($x, $y)\n// query results:\n// all employees' addresses\n```\n\nwill list all the employees addresses.\n\nA pattern can have no variables, in which case the query simply\ndetermines whether that pattern is an entry in the data base.\n\nIf so,\nthere will be one match; if not, there will be no matches.\n\nThe same pattern variable can appear more than once in a query,\nspecifying that the same anything must appear in each\nposition.\n\nThis is why variables have names.\n\nFor example,\n\n```javascript\nsimple_queries_3\n process_query\n sample_data_base_9\n query_driver_loop\n 'supervisor(list(\"Julius\", \"Caesar\"), list(\"Julius\", \"Caesar\"))'\n simple_queries_3_example\n\nsupervisor($x, $x)\n```\n\n```javascript\nsimple_queries_3_example\n\nprocess_query('assert(supervisor(list(\"Julius\", \"Caesar\"), list(\"Julius\", \"Caesar\")))');\nfirst_answer('supervisor($x, $x)');\n\nprocess_query('supervisor($x, $x)');\n\n// query input:\n// supervisor($x, $x)\n// query results:\n// none\n```\n\nfinds all people who supervise themselves (though there are no such assertions in our sample data base).\n\nThe query\n\n```javascript\nsimple_queries_4\n process_query\n sample_data_base_9\n query_driver_loop\n 'job(list(\"Tweakit\", \"Lem\", \"E\"), list(\"computer\", \"technician\"))'\n simple_queries_4_example\n\njob($x, list(\"computer\", $type))\n```", + "token_count": 285, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Deductive Information Retrieval", + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_Deductive_Information_Retrieval_4" + }, + { + "content": "The query\n\nmatches all job entries whose second item is a two-element list whose first item is \"computer\":\n\n```javascript\njob(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))\njob(list(\"Hacker\", \"Alyssa\", \"P\"), list(\"computer\", \"programmer\"))\njob(list(\"Fect\", \"Cy\", \"D\"), list(\"computer\", \"programmer\"))\njob(list(\"Tweakit\", \"Lem\", \"E\"), list(\"computer\", \"technician\"))\n```\n\nThis same pattern does not match job(list(\"Reasoner\", \"Louis\"), list(\"computer\", \"programmer\", \"trainee\")) because the second item in the assertion is a list of three elements, and the patterns second item specifies that there should be two elements.\n\nIf we wanted to change the pattern so that the second item could be any list beginning with \"computer\", we could specify\n\n```javascript\nsimple_queries_5\n process_query\n sample_data_base_9\n query_driver_loop\n 'job(list(\"Reasoner\", \"Louis\"), list(\"computer\", \"programmer\", \"trainee\"))'\n simple_queries_5_example\n\njob($x, pair(\"computer\", $type))\n```\n\n```javascript\nsimple_queries_5_example\n\nfirst_answer('job($x, pair(\"computer\", $type))');\n\nprocess_query('job($x, pair(\"computer\", $type))');\n\n// query input:\n// job($x, pair(\"computer\", $type))\n// query results:\n// job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))\n// job(list(\"Hacker\", \"Alyssa\", \"P\"), list(\"computer\", \"programmer\"))\n// job(list(\"Fect\", \"Cy\", \"D\"), list(\"computer\", \"programmer\"))\n// job(list(\"Tweakit\", \"Lem\", \"E\"), list(\"computer\", \"technician\"))\n// job(list(\"Reasoner\", \"Louis\"), list(\"computer\", \"programmer\", \"trainee\"))\n```\n\nFor example,\n\n```javascript\npair(\"computer\", $type)\n```\n\nmatches the data\n\n```javascript\nlist(\"computer\", \"programmer\", \"trainee\")\n```\n\nwith\n$type\nas\nlist(\"programmer\", \"trainee\").\n\nIt also matches the data\n\n```javascript\nlist(\"computer\", \"programmer\")\n```\n\nwith $type as list(\"programmer\"), and matches the data\n\n```javascript\nlist(\"computer\")\n```\n\nwith $type as the empty list, null.\n\nWe can describe the query language s processing of simple queries as\nfollows:\n-\n-\nThe system finds all assignments to variables in the query\npattern that\nsatisfy the pattern that is, all sets\nof values for the variables such that if the pattern variables are\ninstantiated with (replaced by) the values, the result is\nin the data base.\n-\n-\nThe system responds to the query by listing all instantiations of the\nquery pattern with the variable assignments that satisfy it.\n\nNote that if the pattern has no variables, the query reduces to a\ndetermination of whether that pattern is in the data base.", + "token_count": 311, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Deductive Information Retrieval", + "chunk_index": 5, + "chunk_id": "Metalinguistic_Abstraction_Deductive_Information_Retrieval_5" + }, + { + "content": "Note that if the pattern has no variables, the query reduces to a\ndetermination of whether that pattern is in the data base.\n\nSimple queries form the primitive operations of the query language.\n\nIn order to form compound operations, the query language provides\nmeans of combination.\n\nOne thing that makes the query language a logic\nprogramming language is that the means of combination mirror the means\nof combination used in forming logical expressions:\n, , and.\n\nWe can use as follows to find the addresses of all the computer programmers:\n\n```javascript\ncompound_queries_1\n query_driver_loop\n process_query\n sample_data_base_9\n compound_queries_1_example\n 'and(job(list(\"Fect\", \"Cy\", \"D\"), list(\"computer\", \"programmer\")), address(list(\"Fect\", \"Cy\", \"D\"), list(\"Cambridge\", list(\"Ames\", \"Street\"), 3)))'\n\nand(job($person, list(\"computer\", \"programmer\")),\n address($person, $where))\n```\n\n```javascript\ncompound_queries_1_example\n\nfirst_answer('and(job($person, list(\"computer\", \"programmer\")), address($person, $where))');\n\nprocess_query('and(job($person, list(\"computer\", \"programmer\")), address($person, $where))');\n\n// query input:\n// and(job($person, list(\"computer\", \"programmer\")), address($person, $where))\n// query results:\n// and(job(list(\"Hacker\", \"Alyssa\", \"P\"), list(\"computer\", \"programmer\")),\n// address(list(\"Hacker\", \"Alyssa\", \"P\"), list(\"Cambridge\", list(\"Mass\", \"Ave\"), 78)))\n// and(job(list(\"Fect\", \"Cy\", \"D\"), list(\"computer\", \"programmer\")),\n// address(list(\"Fect\", \"Cy\", \"D\"), list(\"Cambridge\", list(\"Ames\", \"Street\"), 3)))\n```\n\nThe resulting output is\n\n```javascript\nand(job(list(\"Hacker\", \"Alyssa\", \"P\"), list(\"computer\", \"programmer\")),\n address(list(\"Hacker\", \"Alyssa\", \"P\"),\n list(\"Cambridge\", list(\"Mass\", \"Ave\"), 78)))\n\nand(job(list(\"Fect\", \"Cy\", \"D\"), list(\"computer\", \"programmer\")),\n address(list(\"Fect\", \"Cy\", \"D\"),\n list(\"Cambridge\", list(\"Ames\", \"Street\"), 3)))\n```\n\nIn general, and(query$_{1}$, query$_{2}$, $\\ldots$, query$_{n})$ is satisfied by all sets of values for the pattern variables that simultaneously satisfy query$_{1}, \\ldots,$ query$_{n}$.\n\nAs for simple queries, the system processes a compound query by finding all assignments to the pattern variables that satisfy the query, then displaying instantiations\n\nof the query with those values.\n\nAnother means of constructing compound queries is through.\n\nFor example,\n\n```javascript\ncompound_queries_2\n process_query\n query_driver_loop\n sample_data_base_9\n compound_queries_2_example\n 'or(supervisor(list(\"Tweakit\", \"Lem\", \"E\"), list(\"Bitdiddle\", \"Ben\")), supervisor(list(\"Tweakit\", \"Lem\", \"E\"), list(\"Hacker\", \"Alyssa\", \"P\")))'\n\nor(supervisor($x, list(\"Bitdiddle\", \"Ben\")),\n supervisor($x, list(\"Hacker\", \"Alyssa\", \"P\")))\n```", + "token_count": 286, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Deductive Information Retrieval", + "chunk_index": 6, + "chunk_id": "Metalinguistic_Abstraction_Deductive_Information_Retrieval_6" + }, + { + "content": "For example,\n\nwill find all employees supervised by Ben Bitdiddle or Alyssa P.\n\nHacker:\n\n```javascript\nor(supervisor(list(\"Hacker\", \"Alyssa\", \"P\"),\n list(\"Bitdiddle\", \"Ben\")),\n supervisor(list(\"Hacker\", \"Alyssa\", \"P\"),\n list(\"Hacker\", \"Alyssa\", \"P\")))\n\nor(supervisor(list(\"Fect\", \"Cy\", \"D\"),\n list(\"Bitdiddle\", \"Ben\")),\n supervisor(list(\"Fect\", \"Cy\", \"D\"),\n list(\"Hacker\", \"Alyssa\", \"P\")))\n\nor(supervisor(list(\"Tweakit\", \"Lem\", \"E\"),\n list(\"Bitdiddle\", \"Ben\")),\n supervisor(list(\"Tweakit\", \"Lem\", \"E\"),\n list(\"Hacker\", \"Alyssa\", \"P\")))\n\nor(supervisor(list(\"Reasoner\", \"Louis\"),\n list(\"Bitdiddle\", \"Ben\")),\n supervisor(list(\"Reasoner\", \"Louis\"),\n list(\"Hacker\", \"Alyssa\", \"P\")))\n```\n\nIn general, or(query$_{1}$, query$_{2}$, $\\ldots$, query$_{n}$) is satisfied by all sets of values for the pattern variables that satisfy at least one of query$_{1} \\ldots$\n\nquery$_{n}$.\n\nCompound queries can also be formed with.\n\nFor example,\n\n```javascript\ncompound_queries_3\n process_query\n sample_data_base_9\n query_driver_loop\n 'and(supervisor(list(\"Tweakit\", \"Lem\", \"E\"), list(\"Bitdiddle\", \"Ben\")), not(job(list(\"Tweakit\", \"Lem\", \"E\"), list(\"computer\", \"programmer\"))))'\n compound_queries_3_example\n\nand(supervisor($x, list(\"Bitdiddle\", \"Ben\")),\n not(job($x, list(\"computer\", \"programmer\"))))\n```\n\n```javascript\ncompound_queries_3_example\n\nfirst_answer('and(supervisor($x, list(\"Bitdiddle\", \"Ben\")), not(job($x, list(\"computer\", \"programmer\"))))');\n\nprocess_query('and(supervisor($x, list(\"Bitdiddle\", \"Ben\")), not(job($x, list(\"computer\", \"programmer\"))))');\n\n// query input:\n// and(supervisor($x, list(\"Bitdiddle\", \"Ben\")), not(job($x, list(\"computer\", \"programmer\"))))\n// query results:\n// all people supervised by Ben Bitdiddle who are not computer programmers\n```\n\nfinds all people supervised by Ben Bitdiddle who are not computer\nprogrammers.\n\nIn general,\nnot(query$_{1}$)\nis satisfied by all assignments to the pattern variables that do not\nsatisfy\nquery$_{1}$.\n\nThe final combining form starts with\njavascript_predicate and\ncontains a JavaScript predicate.\n\nIn general,\n\n```javascript\njavascript_predicate(predicate)\n```\n\nwill be satisfied by assignments to the pattern variables\nin the predicate for which the\ninstantiated\npredicate is true.\n\nFor example, to find all people whose salary is greater than\n50,000 we could write\n\n```javascript\ncompound_queries_4\n\t process_query\n\t sample_data_base_9\n\t query_driver_loop\n\t compound_queries_4_example\n\t 'and(salary(list(\"Scrooge\", \"Eben\"), 141421), javascript_predicate((141421 > 50000)))'\n\nand(salary($person, $amount), javascript_predicate($amount > 50000))\n```\n\n```javascript\ncompound_queries_4_example\n\nfirst_answer('and(salary($person, $amount), javascript_predicate($amount > 50000))');\n\nprocess_query('and(salary($person, $amount), javascript_predicate($amount > 50000))');\n\n// query input:\n// and(salary($person, $amount), javascript_predicate($amount > 50000))\n// query results:\n// people whose salary is greater than 50000\n```\n\nIn addition to primitive queries and compound queries, the query\nlanguage provides means for\nabstracting queries.\n\nThese are given by\nrules.\n\nThe rule\n\n```javascript\nrules_1\n process_query\n sample_data_base_9\n rule_same\n 'lives_near(list(\"Reasoner\", \"Louis\"), list(\"Aull\", \"DeWitt\"))'\n rules_1_example\n\nrule(lives_near($person_1, $person_2),\n and(address($person_1, pair($town, $rest_1)),\n address($person_2, pair($town, $rest_2)),\n not(same($person_1, $person_2))))\n\nprocess_query(`assert(\nrule(lives_near($person_1, $person_2),\n and(address($person_1, pair($town, $rest_1)),\n address($person_2, pair($town, $rest_2)),\n not(same($person_1, $person_2)))))`);\n```", + "token_count": 342, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Deductive Information Retrieval", + "chunk_index": 7, + "chunk_id": "Metalinguistic_Abstraction_Deductive_Information_Retrieval_7" + }, + { + "content": "The rule\n\nspecifies that two people live near each other if they live in the\nsame town.\n\nThe final clause prevents the\nrule from saying that all people live near themselves.\n\nThe\nrelation is defined by a very simple\nrule:\n\n```javascript\nrule_same\n process_query\n sample_data_base_9\n 'same(list(\"Reasoner\", \"Louis\"), list(\"Reasoner\", \"Louis\"))'\n rule_same_example\n\nrule(same($x, $x))\n\nprocess_query('assert(rule(same($x, $x)))');\n```\n\n```javascript\nrule_same_example\n rule_same\n process_query\n\nfirst_answer('same(list(\"Reasoner\", $first_name), list($surname, \"Louis\"))');\n\nprocess_query('same(list(\"Reasoner\", $first_name), list($surname, \"Louis\"))');\n```\n\nThe following rule declares that a person is a wheel in an organization if he supervises someone who is in turn a supervisor:\n\n```javascript\nrules_2\n process_query\n sample_data_base_9\n rule_same\n 'wheel(list(\"Warbucks\", \"Oliver\"))'\n rules_2_example\n\nrule(wheel($person),\n and(supervisor($middle_manager, $person),\n supervisor($x, $middle_manager)))\n\nprocess_query(`assert(\nrule(wheel($person),\n and(supervisor($middle_manager, $person),\n supervisor($x, $middle_manager))))`);\n```\n\n```javascript\nrules_2_example\n\nfirst_answer('wheel($who)');\n\nprocess_query('wheel($who)');\n```\n\nThe general form of a rule is rule(conclusion, body) where conclusion is a pattern and body is any query.\n\nWe can think of a rule as representing a large (even\ninfinite) set of assertions, namely all instantiations of the rule conclusion\nwith variable assignments that satisfy the rule body.\n\nWhen we described\nsimple queries (patterns), we said that an assignment to variables satisfies\na pattern if the instantiated pattern is in the data base.\n\nBut the pattern\nneedn t be explicitly in the data base as an assertion.\n\nIt\ncan be an\nimplicit assertion implied by a rule.\n\nFor example, the\nquery\n\n```javascript\nrules_3\n query_driver_loop\n sample_data_base_9\n rules_1\n process_query\n 'lives_near(list(\"Aull\", \"DeWitt\"), list(\"Bitdiddle\", \"Ben\"))'\n\nfirst_answer('lives_near($x, list(\"Bitdiddle\", \"Ben\"))');\n\nlives_near($x, list(\"Bitdiddle\", \"Ben\"))\n\nprocess_query('lives_near($x, list(\"Bitdiddle\", \"Ben\"))');\n\n// query input:\n// lives_near($x, list(\"Bitdiddle\", \"Ben\"))\n// query results:\n// lives_near(list(\"Reasoner\", \"Louis\"), list(\"Bitdiddle\", \"Ben\"))\n// lives_near(list(\"Aull\", \"DeWitt\"), list(\"Bitdiddle\", \"Ben\"))\n```\n\nresults in\n\n```javascript\nlives_near(list(\"Reasoner\", \"Louis\"), list(\"Bitdiddle\", \"Ben\"))\nlives_near(list(\"Aull\", \"DeWitt\"), list(\"Bitdiddle\", \"Ben\"))\n```\n\nTo find all computer programmers who live near Ben Bitdiddle, we can ask\n\n```javascript\nrules_4\n query_driver_loop\n sample_data_base_9\n rules_1\n process_query\n 'and(job(list(\"Reasoner\", \"Louis\"), list(\"computer\", \"programmer\", \"trainee\")), lives_near(list(\"Reasoner\", \"Louis\"), list(\"Bitdiddle\", \"Ben\")))'\n\nfirst_answer('and(job($x, pair(\"computer\", $something)), lives_near($x, list(\"Bitdiddle\", \"Ben\")))');\n\nand(job($x, list(\"computer\", \"programmer\")),\n lives_near($x, list(\"Bitdiddle\", \"Ben\")))\n\nprocess_query('and(job($x, list(\"computer\", \"programmer\")), lives_near($x, list(\"Bitdiddle\", \"Ben\")))');\n\n// query input:\n// and(job($x, list(\"computer\", \"programmer\")), lives_near($x, list(\"Bitdiddle\", \"Ben\")))\n// query results:\n```", + "token_count": 329, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Deductive Information Retrieval", + "chunk_index": 8, + "chunk_id": "Metalinguistic_Abstraction_Deductive_Information_Retrieval_8" + }, + { + "content": "To find all computer programmers who live near Ben Bitdiddle, we can ask\n\nFor instance, the rule\n\n```javascript\nrules_5\n query_driver_loop\n sample_data_base_9\n rules_5_example\n\nrule(outranked_by($staff_person, $boss),\n or(supervisor($staff_person, $boss),\n and(supervisor($staff_person, $middle_manager),\n outranked_by($middle_manager, $boss))))\n\nprocess_query(`assert(\nrule(outranked_by($staff_person, $boss),\n or(supervisor($staff_person, $boss),\n and(supervisor($staff_person, $middle_manager),\n outranked_by($middle_manager, $boss)))))`);\n```\n\n```javascript\nrules_5_example\n rules_5\n process_query\n 'outranked_by(list(\"Bitdiddle\", \"Ben\"), list(\"Warbucks\", \"Oliver\"))'\n\nprocess_query(`assert(\nrule(outranked_by($staff_person, $boss),\n or(supervisor($staff_person, $boss),\n and(supervisor($staff_person, $middle_manager),\n outranked_by($middle_manager, $boss)))))`);\nfirst_answer('outranked_by(list($who, \"Ben\"), $the_boss)');\n\nprocess_query('outranked_by(list($who, \"Ben\"), $the_boss)');\n\n// query input:\n// outranked_by(list($who, \"Ben\"), $the_boss)\n// query results:\n// outranked_by(list(\"Bitdiddle\", \"Ben\"), list(\"Warbucks\", \"Oliver\"))\n```\n\nsays that a staff person is outranked by a boss in the organization if the boss is the person s supervisor or (recursively) if the\n\nperson s supervisor is outranked by the boss.\n\nWe can regard a rule as a kind of logical implication: If an\nassignment of values to pattern variables satisfies the body,\nthen it satisfies the conclusion.\n\nConsequently, we can regard the\nquery language as having the ability to perform logical\ndeductions based upon the rules.\n\nAs an example, consider the\noperation described at the beginning of\nsection.\n\nAs we said,\ncan be characterized by the following\ntwo rules:\n-\n-\nFor any list , the empty list and\nto\nform.\n-\n-\nFor any , ,\n, and ,\npair(u, v)\nand\nto form\npair(u, z)\nif and\nto form.\n\nTo express this in our query language, we define two rules for a relation\n\n```javascript\nappend_to_form(x, y, z)\n```\n\nwhich we can interpret to mean and to form :\n\n```javascript\nappend_to_form\n process_query\n 'append_to_form(list(1, 2), list(3, 4), list(1, 2, 3, 4))'\n append_to_form_example_1\n\nrule(append_to_form(null, $y, $y))\n\nrule(append_to_form(pair($u, $v), $y, pair($u, $z)),\n append_to_form($v, $y, $z))\n\nprocess_query(`assert(\nrule(append_to_form(null, $y, $y)))`);\nprocess_query(`assert(\nrule(append_to_form(pair($u, $v), $y, pair($u, $z)),\n append_to_form($v, $y, $z)))`);\n```\n\n```javascript\nappend_to_form_just_the_rules\n\nprocess_query(`assert(\nrule(append_to_form(null, $y, $y)))`);\nprocess_query(`assert(\nrule(append_to_form(pair($u, $v), $y, pair($u, $z)),\n append_to_form($v, $y, $z)))`);\n```", + "token_count": 295, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Deductive Information Retrieval", + "chunk_index": 9, + "chunk_id": "Metalinguistic_Abstraction_Deductive_Information_Retrieval_9" + }, + { + "content": "which we can interpret to mean and to form :\n\nThe first rule has\nno body, which means that the conclusion holds for\nany value of\n$y.\n\nNote how the second rule makes use of\npair to name the\nhead\nand\ntail\nof a list.\n\nGiven these two rules, we can formulate queries that compute the of two lists:\n\n```javascript\nappend_to_form_example_2\n process_query\n append_to_form_just_the_rules\n 'append_to_form(list(\"a\", \"b\"), list(\"c\", \"d\"), list(\"a\", \"b\", \"c\", \"d\"))'\n\nQuery input:\n\nfirst_answer('append_to_form(list(\"a\", \"b\"), list(\"c\", \"d\"), z)');\n\nappend_to_form(list(\"a\", \"b\"), list(\"c\", \"d\"), $z)\n\nprocess_query('append_to_form(list(\"a\", \"b\"), list(\"c\", \"d\"), $z)');\n\n// query input:\n// append_to_form(list(\"a\", \"b\"), list(\"c\", \"d\"), $z)\n// query results:\n// append_to_form(list(\"a\", \"b\"), list(\"c\", \"d\"), list(\"a\", \"b\", \"c\", \"d\"))\n\nQuery results:\nappend_to_form(list(\"a\", \"b\"), list(\"c\", \"d\"), list(\"a\", \"b\", \"c\", \"d\"))\n```\n\nWhat is more striking, we can use the same rules to ask the question\nWhich list, when ed to list(\"a\", \"b\"), yields list(\"a\", \"b\", \"c\", \"d\")?\n\nThis is done as follows:\n\n```javascript\nappend_to_form_example_3\n process_query\n append_to_form_just_the_rules\n 'append_to_form(list(\"a\", \"b\"), list(\"c\", \"d\"), list(\"a\", \"b\", \"c\", \"d\"))'\n\nfirst_answer('append_to_form(list(\"a\", \"b\"), y, list(\"a\", \"b\", \"c\", \"d\"))');\n\nQuery input:\n\nappend_to_form(list(\"a\", \"b\"), $y, list(\"a\", \"b\", \"c\", \"d\"))\n\nprocess_query('append_to_form(list(\"a\", \"b\"), $y, list(\"a\", \"b\", \"c\", \"d\"))');\n\n// query input:\n// append_to_form(list(\"a\", \"b\"), $y, list(\"a\", \"b\", \"c\", \"d\"))\n// query results:\n// append_to_form(list(\"a\", \"b\"), list(\"c\", \"d\"), list(\"a\", \"b\", \"c\", \"d\"))\n\nQuery results:\nappend_to_form(list(\"a\", \"b\"), list(\"c\", \"d\"), list(\"a\", \"b\", \"c\", \"d\"))\n```\n\nWe can ask for all pairs of lists that to form list(\"a\", \"b\", \"c\", \"d\"):", + "token_count": 235, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Deductive Information Retrieval", + "chunk_index": 10, + "chunk_id": "Metalinguistic_Abstraction_Deductive_Information_Retrieval_10" + }, + { + "content": "We can ask for all pairs of lists that to form list(\"a\", \"b\", \"c\", \"d\"):\n\nThe query system may seem to exhibit quite a bit of intelligence in\nusing the rules to deduce the answers to the queries above.\n\nActually,\nas we will see in the next section, the system is following a\nwell-determined algorithm in unraveling the rules.\n\nUnfortunately,\nalthough the system works impressively in the\ncase, the general methods may break down\nin more complex cases, as we will see\nin section.", + "token_count": 84, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Deductive Information Retrieval", + "chunk_index": 11, + "chunk_id": "Metalinguistic_Abstraction_Deductive_Information_Retrieval_11" + }, + { + "content": "In section we will\npresent an implementation of the query interpreter as a collection of\nfunctions.\n\nIn this section we give an overview that explains the general\nstructure of the system independent of low-level implementation\ndetails.\n\nAfter describing the implementation of the interpreter, we\nwill be in a position to understand some of its limitations and some\nof the subtle ways in which the query language s logical operations\ndiffer from the operations of mathematical logic.\n\nIt should be apparent that the query evaluator must perform some kind\nof search in order to match queries against facts and rules in the\ndata base.\n\nOne way to do this would be to implement the query system\nas a nondeterministic program, using the\nevaluator of section\n(see exercise ).\n\nAnother possibility\nis to manage the search with the aid of streams.\n\nOur implementation follows\nthis second approach.\n\nThe query system is organized around two central operations, called\npattern matching and unification.\n\nWe first describe\npattern matching and explain how this operation, together with the\norganization of information in terms of streams of frames, enables us\nto implement both simple and compound queries.\n\nWe next discuss\nunification, a generalization of pattern matching needed to implement\nrules.\n\nFinally, we show how the entire query interpreter fits\ntogether through a\nfunction\nthat classifies\nqueries\nin a manner analogous to the way\nevaluate\nclassifies expressions for the interpreter described in\nsection.\n\nA pattern matcher is a program that tests whether some datum\nfits a specified pattern.\n\nFor example, the\ndatum list(list(\"a\", \"b\"), \"c\", list(\"a\", \"b\"))\nmatches the pattern\nlist($x, \"c\", $x)\nwith the pattern variable\n$x\nbound to\nlist(\"a\", \"b\").\n\nThe same\ndata list\nmatches the pattern\nlist($x, $y, $z)\nwith\n$x\nand\n$z\nboth bound to\nlist(\"a\", \"b\")\nand\n$y\nbound to\n\"c\".", + "token_count": 298, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "How the Query System Works", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_How_the_Query_System_Works_1" + }, + { + "content": "The same\ndata list\nmatches the pattern\nlist($x, $y, $z)\nwith\n$x\nand\n$z\nboth bound to\nlist(\"a\", \"b\")\nand\n$y\nbound to\n\"c\".\n\nHowever, it does not match the pattern\nlist($x, \"a\", $y),\nsince that pattern specifies a list whose second element is the\nstring \"a\".\n\nThe pattern matcher used by the query system takes as inputs a\npattern, a datum, and a\nframe that specifies bindings for\nvarious pattern variables.\n\nIt checks whether the datum matches the\npattern in a way that is consistent with the bindings already in the\nframe.\n\nIf so, it returns the given frame augmented by any bindings\nthat may have been determined by the match.\n\nOtherwise, it indicates\nthat the match has failed.\n\nUsing the pattern\nlist($x, $y, $x)\nto match\nlist(\"a\", \"b\", \"a\")\ngiven an empty frame, for example,\nwill return a frame specifying that\n$x\nis bound to\n\"a\"\nand\n$y\nis bound to\n\"b\".\n\nTrying the match with the same pattern, the same datum, and a frame\nspecifying that\n$y\nis bound to\n\"a\"\nwill fail.\n\nTrying the match with the same pattern, the same datum, and a\nframe in which\n$y\nis bound to\n\"b\"\nand\n$x\nis unbound will return the given frame augmented by a binding of\n$x\nto \"a\".\n\nThe pattern matcher is all the mechanism that is needed to process\nsimple\nqueries that don t involve rules.\n\nFor instance, to process the query\n\n```javascript\njob($x, list(\"computer\", \"programmer\"))\n```\n\nwe scan through all assertions in the data base and select those that\nmatch the pattern with respect to an initially empty frame.\n\nFor each\nmatch we find, we use the frame returned by the match to instantiate\nthe pattern with a value for\n$x.\n\nThe testing of patterns against frames is organized through the use of\nstreams.", + "token_count": 301, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "How the Query System Works", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_How_the_Query_System_Works_2" + }, + { + "content": "The testing of patterns against frames is organized through the use of\nstreams.\n\nFor each data-base entry, the matcher\ngenerates either a special symbol indicating that the match has failed\nor an extension to the frame.\n\nThe results for all the data-base\nentries are collected into a stream, which is passed through a filter\nto weed out the failures.\n\nThe result is a stream of all the frames\nthat extend the given frame via a match to some assertion in the data\nbase.\n\nIn our system, a query takes an input stream of frames and performs\nthe above matching operation for every frame in the stream, as\nindicated in\nfigure.\n\nThat is, for\neach frame in the input stream, the query generates a new stream consisting\nof all extensions to that frame by matches to assertions in the data base.\n\nAll these streams are then combined to form one huge stream, which contains\nall possible extensions of every frame in the input stream.\n\nThis stream is\nthe output of the query.\n\nA query processes a stream of frames.\n\nTo answer a\nsimple query, we use the query with an input stream\nconsisting of a single empty frame.\n\nThe resulting output stream\ncontains all extensions to the empty frame (that is, all answers to\nour query).\n\nThis stream of frames is then used to generate a stream\nof copies of the original query pattern with the variables\ninstantiated by the values in each frame, and this is the stream that\nis finally printed.\n\nThe real elegance of the stream-of-frames implementation is evident\nwhen we deal with compound queries.\n\nThe processing of compound\nqueries makes use of the ability of our matcher to demand that a match\nbe consistent with a specified frame.", + "token_count": 291, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "How the Query System Works", + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_How_the_Query_System_Works_3" + }, + { + "content": "The processing of compound\nqueries makes use of the ability of our matcher to demand that a match\nbe consistent with a specified frame.\n\n```javascript\nand(can_do_job($x, list(\"computer\", \"programmer\", \"trainee\")),\n job($person, $x))\n```\n\n(informally, Find all people who can do the job of a computer programmer trainee ), we first find all entries that match the pattern\n\n```javascript\ncan_do_job($x, list(\"computer\", \"programmer\", \"trainee\"))\n```\n\nThis produces a stream of frames, each of which contains a binding for\n$x.\n\nThen for each frame in the stream we find all entries that\nmatch\n\n```javascript\njob($person, $x)\n```\n\nin a way that is consistent with the given binding for\n$x.\n\nEach such match will produce a frame containing bindings for\n$x\nand\n$person.\n\nThe of two queries can be viewed as a series\ncombination of the two component queries, as shown in\nfigure.\n\nThe frames that pass through the\nfirst query filter are filtered and further extended by the second query.\n\nThe combination of two queries is produced by operating on the stream of frames in series.\n\nFigure\nshows the analogous method for\ncomputing the\nof two queries as a parallel\ncombination of the two component queries.\n\nThe input stream of frames is\nextended separately by each query.\n\nThe two resulting streams are then\nmerged to produce the final output stream.\n\nThe combination of two queries is produced by operating on the stream of frames in parallel and merging the results.\n\nEven from this high-level description, it is apparent that the\nprocessing of compound queries can be slow.", + "token_count": 253, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "How the Query System Works", + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_How_the_Query_System_Works_4" + }, + { + "content": "Even from this high-level description, it is apparent that the\nprocessing of compound queries can be slow.\n\nThough systems for handling only simple queries are quite practical, dealing\nwith complex queries is extremely difficult.\n\nFrom the stream-of-frames viewpoint, the\nof\nsome query acts as a filter that removes all frames for which the query can\nbe satisfied.\n\nFor instance, given the pattern\n\n```javascript\nnot(job($x, list(\"computer\", \"programmer\")))\n```\n\nwe attempt, for each frame in the input stream, to produce extension\nframes that satisfy\njob($x, list(\"computer\", \"programmer\")).\n\nWe remove from the input stream all frames for which such extensions exist.\n\nThe result is a stream consisting of only those frames in which the binding\nfor\n$x\ndoes not satisfy\njob($x, list(\"computer\", \"programmer\")).\n\nFor example, in processing the query\n\n```javascript\nand(supervisor($x, $y),\n not(job($x, list(\"computer\", \"programmer\"))))\n```\n\nthe first clause will generate frames with bindings for\n$x\nand\n$y.\n\nThe clause will then filter these by\nremoving all frames in which the binding for\n$x\nsatisfies the restriction that\n$x\nis a computer programmer.\n\nThe\njavascript_predicate syntactic form\nis implemented as a similar filter on frame streams.\n\nWe use each frame in\nthe stream to instantiate any variables in the pattern, then apply the\nJavaScript\npredicate.\n\nWe remove from the input stream all frames for which the\npredicate fails.\n\nIn order to handle rules in the query language, we must be able to\nfind the rules whose conclusions match a given query pattern.\n\nRule\nconclusions are like assertions except that they can contain\nvariables, so we will need a generalization of pattern\nmatching called unification in which both the\npattern and the datum may contain variables.\n\nA unifier takes two patterns, each containing constants and variables,\nand determines whether it is possible to assign values to the\nvariables that will make the two patterns equal.", + "token_count": 301, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "How the Query System Works", + "chunk_index": 5, + "chunk_id": "Metalinguistic_Abstraction_How_the_Query_System_Works_5" + }, + { + "content": "A unifier takes two patterns, each containing constants and variables,\nand determines whether it is possible to assign values to the\nvariables that will make the two patterns equal.\n\nFor example, unifying\nlist($x, \"a\", $y)\nand\nlist($y, $z, \"a\")\nwill specify a frame in which\n$x,\n$y,\nand\n$z\nmust all be bound to\n\"a\".\n\nOn the other hand, unifying\nlist($x, $y, \"a\")\nand\nlist($x, \"b\", $y)\nwill fail, because there is no value for\n$y\nthat can make the two patterns equal.\n\n(For the second elements of the\npatterns to be equal,\n$y\nwould have to be\n\"b\";\nhowever, for the third elements to be equal,\n$y\nwould have to be\n\"a\".)\nThe unifier used in the query system, like the pattern matcher, takes a\nframe as input and performs unifications that are consistent with this frame.\n\nThe unification algorithm is the most technically difficult part of\nthe query system.\n\nWith complex patterns, performing unification may\nseem to require deduction.\n\nTo unify\nlist($x, $x)\nand\nlist(list(\"a\", $y, \"c\"), list(\"a\", \"b\", $z))\nfor example,\nthe algorithm must infer that\n$x\nshould be\nlist(\"a\", \"b\", \"c\"),\n$y\nshould be\n\"b\",\nand\n$z\nshould be\n\"c\".\n\nWe may think of this process as solving a set of equations among the pattern\ncomponents.\n\nIn general, these are simultaneous equations, which may require\nsubstantial manipulation to solve.", + "token_count": 223, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "How the Query System Works", + "chunk_index": 6, + "chunk_id": "Metalinguistic_Abstraction_How_the_Query_System_Works_6" + }, + { + "content": "In general, these are simultaneous equations, which may require\nsubstantial manipulation to solve.\n\nIn a successful pattern match, all pattern variables become bound, and\nthe values to which they are bound contain only constants.\n\nThis is\nalso true of all the examples of unification we have seen so far.\n\nIn general, however, a successful unification may not completely\ndetermine the variable values; some variables may remain unbound and\nothers may be bound to values that contain variables.\n\nConsider the unification of\nlist($x, \"a\")\nand\nlist(list(\"b\", $y), $z).\n\nWe can deduce that\n$x\n$=$\nlist(\"b\", $y)\nand\n\"a\"\n$=$\n$z,\nbut we cannot further solve for\n$x\nor\n$y.\n\nThe unification doesn t fail, since it is certainly possible to make\nthe two patterns equal by assigning values to\n$x\nand\n$y.\n\nSince this match in no way restricts the values\n$y\ncan take on, no binding for\n$y\nis put into the result frame.\n\nThe match does, however, restrict the value of\n$x.\n\nWhatever value\n$y\nhas,\n$x\nmust be\nlist(\"b\", $y).\n\nA binding of\n$x\nto the pattern\nlist(\"b\", $y)\nis thus put into the frame.\n\nIf a value for\n$y\nis later determined and added to the frame (by a pattern match or\nunification that is required to be consistent with this frame), the\npreviously bound\n$x\nwill refer to this value.\n\nUnification is the key to the component of the query system that makes\ninferences from rules.\n\nTo see how this is accomplished, consider\nprocessing a query that involves applying a rule, such as\n\n```javascript\nlives_near($x, list(\"Hacker\", \"Alyssa\", \"P\"))\n```\n\nTo process this query, we first use the ordinary pattern-match\nfunction\ndescribed above to see if there are any assertions in the data base that\nmatch this pattern.", + "token_count": 291, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "How the Query System Works", + "chunk_index": 7, + "chunk_id": "Metalinguistic_Abstraction_How_the_Query_System_Works_7" + }, + { + "content": "To process this query, we first use the ordinary pattern-match\nfunction\ndescribed above to see if there are any assertions in the data base that\nmatch this pattern.\n\nWe find that the pattern unifies with the conclusion of the rule\n\n```javascript\nrule(lives_near($person_1, $person_2),\n and(address($person_1, pair($town, $rest_1)),\n address($person_2, list($town, $rest_2)),\n not(same($person_1, $person_2))))\n```\n\nresulting in a frame specifying that\n$x should be bound to (have the same value as) $person_1 and that $person_2 is bound to list(\"Hacker\", \"Alyssa\", \"P\").\n\nNow, relative to this frame, we evaluate the compound query given by the body\nof the rule.\n\nSuccessful matches will extend this frame by providing a\nbinding for\n$person_1,\nand consequently a value for\n$x,\nwhich we can use to instantiate the original query pattern.\n\nIn general, the query evaluator uses the following method to apply a rule when trying to establish a query pattern in a frame that specifies\n\nbindings for some of the pattern variables: - - Unify the query with the conclusion of the rule to form, if successful, an extension of\n\nthe original frame. - - Relative to the extended frame, evaluate the query formed by the body of the rule.\n\nNotice how similar this is to the method for applying a\nfunction\nin the\nevaluate/\\linebreak[2]apply\nevaluator for\nJavaScript:\n-\n-\nBind the\nfunctions\nparameters to its arguments to form a frame that extends the original\nfunction\nenvironment.\n-\n-\nRelative to the extended environment, evaluate the expression\nformed by the body of the\nfunction.\n\nThe similarity between the two evaluators should come as no surprise.\n\nJust as\nfunction\ndefinitions are the means of abstraction in\nJavaScript,\nrule definitions are the means of abstraction in the query language.\n\nIn each case, we unwind the abstraction by creating appropriate\nbindings and evaluating the rule or\nfunction\nbody relative to these.", + "token_count": 301, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "How the Query System Works", + "chunk_index": 8, + "chunk_id": "Metalinguistic_Abstraction_How_the_Query_System_Works_8" + }, + { + "content": "In each case, we unwind the abstraction by creating appropriate\nbindings and evaluating the rule or\nfunction\nbody relative to these.\n\nNow that we have seen how to apply rules, we can\ndescribe how to evaluate simple queries by using both rules and\nassertions.\n\nGiven the query pattern and a stream of frames, we produce, for each\nframe in the input stream, two streams:\n-\n- a stream of extended frames obtained by matching the pattern\nagainst all assertions in the data base (using the pattern matcher),\nand\n-\n- a stream of extended frames obtained by applying all\npossible rules (using the unifier).\n\nAppending these two streams produces a stream that consists of all the\nways that the given pattern can be satisfied consistent with the\noriginal frame.\n\nThese streams (one for each frame in the input\nstream) are now all combined to form one large stream, which therefore\nconsists of all the ways that any of the frames in the original input\nstream can be extended to produce a match with the given pattern.\n\nDespite the complexity of the underlying matching operations, the\nsystem is organized much like an\nevaluator for any language.\n\nThe\nfunction\nthat coordinates the matching operations is called\nevaluate_query,\nand it plays a role analogous to that of the\nevaluate\nfunction\nfor\nJavaScript.\n\nThe function evaluate_query\ntakes as inputs a query and a stream of frames.\n\nIts output is a stream of\nframes, corresponding to successful matches to the query pattern, that\nextend some frame in the input stream, as indicated in\nfigure.\n\nLike\nevaluate,\nevaluate_query\nclassifies the different types of expressions (queries) and dispatches to an\nappropriate\nfunction\nfor each.\n\nThere is a\nfunction\nfor each\nsyntactic\nform\n(, ,\n, and\njavascript_predicate)\nand one for simple queries.", + "token_count": 295, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "How the Query System Works", + "chunk_index": 9, + "chunk_id": "Metalinguistic_Abstraction_How_the_Query_System_Works_9" + }, + { + "content": "There is a\nfunction\nfor each\nsyntactic\nform\n(, ,\n, and\njavascript_predicate)\nand one for simple queries.\n\nFor each query, it calls\nevaluate_query\nwith the query and a stream that consists of a single empty frame.\n\nThis\nwill produce the stream of all possible matches (all possible extensions to\nthe empty frame).\n\nFor each frame in the resulting stream, it instantiates\nthe original query using the values of the variables found in the frame.\n\nThis stream of instantiated queries is then printed.\n\nThe driver also checks for the special command\nassert,\nwhich signals that the input is not a query but rather an assertion or rule\nto be added to the data base.\n\nFor instance,\n\n```javascript\nassert(job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\")))\n\nassert(rule(wheel($person),\n and(supervisor($middle_manager, $person),\n supervisor($x, $middle_manager))))\n```", + "token_count": 127, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "How the Query System Works", + "chunk_index": 10, + "chunk_id": "Metalinguistic_Abstraction_How_the_Query_System_Works_10" + }, + { + "content": "Section described how the query\nsystem works.\n\nNow we fill in the details by presenting a complete\nimplementation of the system.\n\nThe\ndriver loop for the query system repeatedly reads input expressions.\n\nIf the expression is a rule or assertion to be added to\nthe data base, then the information is added.\n\nOtherwise the\nexpression is assumed to be a query.\n\nThe driver passes this query to\nevaluate_query\ntogether with an initial frame stream consisting of a single empty frame.\n\nThe result of the evaluation is a stream of frames generated by satisfying\nthe query with variable values found in the data base.\n\nThese frames are\nused to form a new stream consisting of copies of the original query in\nwhich the variables are instantiated with values supplied by the stream of\nframes, and this final stream is\ndisplayed:\n\n```javascript\nlp_header\n\n// functions from SICP JS 4.4.4\n```\n\n```javascript\nquery_driver_loop\n\tfunctions_4_1_1\n\tfunctions_4_1_2\n\tfunctions_4_1_3\n\tfunctions_4_1_4\n\tlp_header\n\tis_assertion\n\tinstantiate\n\tevaluate_query\n\tsingleton_stream\n\tadd_rule_or_assertion\n\tput_and\n\tdisjoin\n\tnegate\n\tjavascript_predicate\n\tdisplay_stream\n\talways_true\n\tis_variable_2\n\tis_variable_4\n\tconvert_to_query_syntax\n\tunparse\n\tuser_read\n\tquery_driver_loop_example\n\nconst input_prompt = \"Query input:\";\nconst output_prompt = \"Query results:\";\n\nfunction query_driver_loop() {\n const input = user_read(input_prompt) + \";\";\n if (is_null(input)) {\n display(\"evaluator terminated\");\n } else {\n\tconst expression = parse(input);\n const query = convert_to_query_syntax(expression);\n if (is_assertion(query)) {\n add_rule_or_assertion(assertion_body(query));\n display(\"Assertion added to data base.\");\n } else {\n display(output_prompt);\n display_stream(\n stream_map(\n frame =>\n unparse(instantiate_expression(expression, frame)),\n evaluate_query(query, singleton_stream(null))));\n }\n return query_driver_loop();\n }\n}\n\nconst input_prompt = \"Query input:\";\n\nfunction query_driver_loop() {\n const input = user_read(input_prompt);\n if (is_null(input)) {\n display(\"--- evaluator terminated ---\");\n } else {\n const exp = parse(input + \";\");\n const q = convert_to_query_syntax(exp);\n display(\"---- driver loop input -----\");\n display(unparse(exp));\n if (is_assertion(q)) {\n add_rule_or_assertion(assertion_body(q));\n display(\"Assertion added to data base.\");\n } else {\n display(\"------ query results -------\", \"\");\n display_stream(\n stream_map(\n frame => unparse(instantiate_expression(exp, frame)),\n evaluate_query(q, singleton_stream(null))));\n }\n return query_driver_loop();\n }\n}\n```", + "token_count": 302, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 1, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_1" + }, + { + "content": "These frames are\nused to form a new stream consisting of copies of the original query in\nwhich the variables are instantiated with values supplied by the stream of\nframes, and this final stream is\ndisplayed:\n\n```javascript\nprocess_query_example_1\n\nparse_query_verbose('assert(son(\"Adam\", \"Cain\"))');\nparse_query_verbose('son(\"Adam\", x)');\n```\n\n```javascript\nprocess_query\n\tquery_driver_loop\n\tprocess_query_example_1\n\nfunction process_query(input) {\n if (is_null(input)) {\n display(\"--- evaluator terminated ---\");\n } else {\n const exp = parse(input + \";\");\n const q = convert_to_query_syntax(exp);\n display(\"---- driver loop input -----\");\n display(unparse(exp));\n if (is_assertion(q)) {\n add_rule_or_assertion(assertion_body(q));\n display(\"Assertion added to data base.\");\n } else {\n display(\"------ query results -------\", \"\");\n display_stream(\n stream_map(\n frame => unparse(instantiate_expression(exp, frame)),\n evaluate_query(q, singleton_stream(null))));\n }\n }\n}\n\nfunction first_answer(input) {\n const exp = parse(input + \";\");\n const q = convert_to_query_syntax(exp);\n const frames = evaluate_query(q, singleton_stream(null));\n return is_null(frames)\n ? \"no matching data\"\n : unparse(instantiate_expression(exp, head(frames)));\n}\n\nfunction process_query(input) {\n if (is_null(input)) {\n display(\"--- evaluator terminated ---\");\n } else {\n const exp = parse(input + \";\");\n const q = convert_to_query_syntax(exp);\n if (is_assertion(q)) {\n add_rule_or_assertion(assertion_body(q));\n } else {\n display(\"------ query results -------\", \"\");\n display_stream(\n stream_map(\n frame => unparse(instantiate_expression(exp, frame)),\n evaluate_query(q, singleton_stream(null))));\n }\n }\n}\n\nfunction first_answer(input) {\n const exp = parse(input + \";\");\n const q = convert_to_query_syntax(exp);\n const frames = evaluate_query(q, singleton_stream(null));\n return is_null(frames)\n ? \"no matching data\"\n : unparse(instantiate_expression(exp, head(frames)));\n}\n```\n\nHere, as in the other evaluators in this chapter, we use parse to transform a component of the query language given as a string into a JavaScript syntax representation.\n\n(We append a semicolon to the input expression string because parse expects a statement.) Then we further transform the syntax representation to a conceptual level appropriate for the query system using convert_to_query_syntax, which is declared in section along with the predicate is_assertion and the selector assertion_body.\n\nThe function add_rule_or_assertion is declared in section.", + "token_count": 289, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 2, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_2" + }, + { + "content": "The function add_rule_or_assertion is declared in section.\n\nThe functions instantiate_expression and unparse are declared in section.\n\nThe\nevaluate_query\nfunction,\ncalled by the\nquery_driver_loop,\nis the basic evaluator of the query system.\n\nIt takes as inputs a query\nand a stream of frames, and it returns a stream of extended frames.\n\nIt identifies\nsyntactic\nforms by a\ndata-directed dispatch using and\n, just as we did in implementing generic\noperations in chapter.\n\nAny query that is not identified as a\nsyntactic\nform is assumed to be a simple query, to be processed by\nsimple_query.\n\n```javascript\nevaluate_query\n\toperation_table_from_chapter_3\n\toperation_table\n\tsimple_query\n\ttype\n\tappend_to_form_example_5\n\nfunction evaluate_query(query, frame_stream) {\n const qfun = get(type(query), \"evaluate_query\");\n return is_undefined(qfun)\n ? simple_query(query, frame_stream)\n : qfun(contents(query), frame_stream);\n}\n```\n\nThe functions type and , defined in section , implement the abstract syntax of the syntactic forms.\n\nThe\nsimple_query\nfunction\nhandles simple queries.\n\nIt takes as arguments a simple query (a pattern)\ntogether with a stream of frames, and it returns the stream formed by\nextending each frame by all data-base matches of the query.\n\n```javascript\nsimple_query\n\tstream_flatmap\n\tfind_assertions\n\tapply_rules\n\tappend_to_form_example_5\n\nfunction simple_query(query_pattern, frame_stream) {\n return stream_flatmap(\n frame =>\n stream_append_delayed(\n find_assertions(query_pattern, frame),\n () => apply_rules(query_pattern, frame)),\n frame_stream);\n}\n```\n\nFor each frame in the input stream, we use\nfind_assertions\n(section ) to match the pattern\nagainst all assertions in the data base, producing a stream of extended\nframes, and we use\napply_rules\n(section ) to apply\nall possible rules, producing another stream of extended frames.\n\nThese two streams are combined (using\nstream_append_delayed,\nsection ) to make a stream of all\nthe ways that the given pattern can be satisfied consistent with the\noriginal frame (see exercise ).", + "token_count": 276, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_3" + }, + { + "content": "These two streams are combined (using\nstream_append_delayed,\nsection ) to make a stream of all\nthe ways that the given pattern can be satisfied consistent with the\noriginal frame (see exercise ).\n\nWe handle and queries as illustrated in figure with the\nfunction, which\ntakes as inputs the conjuncts and the frame stream and returns the stream\nof extended frames.\n\nFirst, processes\nthe stream of frames to find the stream of all possible frame extensions\nthat satisfy the first query in the conjunction.\n\nThen, using this as the\nnew frame stream, it recursively applies\nto the rest of the queries.\n\n```javascript\nconjoin\n\tis_empty_conjunction\n\toperation_table_from_chapter_3\n\toperation_table\n\tis_empty_conjunction\n\tstream_append_delayed\n\tappend_to_form_example_5\n\nfunction conjoin(conjuncts, frame_stream) {\n return is_empty_conjunction(conjuncts)\n ? frame_stream\n : conjoin(rest_conjuncts(conjuncts),\n evaluate_query(first_conjunct(conjuncts),\n frame_stream));\n}\n```\n\nThe statement\n\n```javascript\nput_and\n\tconjoin\n\tappend_to_form_example_5\n\nput(\"and\", \"evaluate_query\", conjoin);\n```\n\nsets up evaluate_query to dispatch to when an is encountered.\n\nWe handle queries\nsimilarly, as shown in\nfigure.\n\nThe output streams for the various disjuncts of the\nare computed separately and merged using\nthe\ninterleave_delayed\nfunction\nfrom section.\n\n(See exercises\nand.)\n\n```javascript\ndisjoin\n\toperation_table_from_chapter_3\n\toperation_table\n\tis_empty_conjunction\n\tstream_append_delayed\n\tappend_to_form_example_5\n\nfunction disjoin(disjuncts, frame_stream) {\n return is_empty_disjunction(disjuncts)\n ? null\n : interleave_delayed(\n evaluate_query(first_disjunct(disjuncts), frame_stream),\n () => disjoin(rest_disjuncts(disjuncts), frame_stream));\n}\nput(\"or\", \"evaluate_query\", disjoin);\n```\n\nThe predicates and selectors for the representation of conjuncts and disjuncts are given in section.\n\nThe syntactic form is\nhandled by the method outlined in\nsection.\n\nWe attempt to extend\neach frame in the input stream to satisfy the query being negated, and we\ninclude a given frame in the output stream only if it cannot be extended.\n\n```javascript\nnegate\n\toperation_table_from_chapter_3\n\toperation_table\n\tstream_flatmap\n\tsingleton_stream\n\tappend_to_form_example_5\n\nfunction negate(exps, frame_stream) {\n return stream_flatmap(\n frame =>\n is_null(evaluate_query(negated_query(exps),\n singleton_stream(frame)))\n ? singleton_stream(frame)\n : null,\n frame_stream);\n}\nput(\"not\", \"evaluate_query\", negate);\n```\n\nThe javascript_predicate syntactic form\nis a filter similar to.", + "token_count": 295, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_4" + }, + { + "content": "The javascript_predicate syntactic form\nis a filter similar to.\n\nThe instantiated predicate is evaluated using evaluate from section with the_global_environment and thus can handle any JavaScript expression, as long as all pattern variables are instantiated prior to evaluation.\n\n```javascript\ncompound_queries_5_example\n\t compound_queries_4\n\t process_query\n\nfirst_answer('and(salary(person, amount), javascript_predicate(amount > 50000))');\n// parse_query_verbose('and(salary(person, amount), javascript_predicate(amount > 50000))', \"verbose\");\n```\n\n```javascript\njavascript_predicate\n\t operation_table_from_chapter_3\n\t operation_table\n\t stream_flatmap\n\t singleton_stream\n\t compound_queries_5_example\n\nfunction javascript_predicate(exps, frame_stream) {\n return stream_flatmap(\n frame =>\n evaluate(instantiate_expression(\n javascript_predicate_expression(exps),\n frame),\n the_global_environment)\n ? singleton_stream(frame)\n : null,\n frame_stream);\n}\nput(\"javascript_predicate\", \"evaluate_query\", javascript_predicate);\n```\n\n```javascript\nexecute\n\tfunctions_4_1_1\n\tfunctions_4_1_2\n\tfunctions_4_1_3\n\tfunctions_4_1_4\n\tis_empty_conjunction\n\tcompound_queries_5_example\n```\n\nThe\nalways_true syntactic form\nprovides for a query that is always satisfied.\n\nIt ignores its contents\n(normally empty) and simply passes through all the frames in the input\nstream.\n\nThe rule_body selector (section) uses always_true\nto provide bodies for rules that were defined without bodies (that is,\nrules whose bodies are always satisfied).\n\n```javascript\nalways_true\n\toperation_table_from_chapter_3\n\toperation_table\n\tappend_to_form_example_5\n\nfunction always_true(ignore, frame_stream) {\n return frame_stream;\n}\nput(\"always_true\", \"evaluate_query\", always_true);\n```\n\nThe selectors that define the syntax of and javascript_predicate are given in section.\n\nThe function find_assertions,\ncalled by\nsimple_query\n(section ), takes as input a pattern\nand a frame.\n\nIt returns a stream of frames, each extending the given one\nby a data-base match of the given pattern.\n\nIt uses\nfetch_assertions\n(section ) to get a stream of all the\nassertions in the data base that should be checked for a match against the\npattern and the frame.\n\nThe reason for\nfetch_@assertions\nhere is that we can often apply simple tests that will eliminate many of\nthe entries in the data base from the pool of candidates for a successful\nmatch.", + "token_count": 273, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 5, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_5" + }, + { + "content": "The reason for\nfetch_@assertions\nhere is that we can often apply simple tests that will eliminate many of\nthe entries in the data base from the pool of candidates for a successful\nmatch.\n\n```javascript\nfind_assertions\n\tstream_flatmap\n\tcheck_an_assertion\n\tfetch_assertions\n\tappend_to_form_example_5\n\nfunction find_assertions(pattern, frame) {\n return stream_flatmap(\n datum => check_an_assertion(datum, pattern, frame),\n fetch_assertions(pattern, frame));\n}\n```\n\nThe function check_an_assertion takes as arguments a data object (an assertion), a pattern, and a frame and returns either a one-element stream containing the extended\n\nframe or null if the match fails.\n\n```javascript\ncheck_an_assertion\n\tpattern_match\n\tsingleton_stream\n\tappend_to_form_example_5\n\nfunction check_an_assertion(assertion, query_pat, query_frame) {\n const match_result = pattern_match(query_pat, assertion,\n query_frame);\n return match_result === \"failed\"\n ? null\n : singleton_stream(match_result);\n}\n```\n\nThe basic pattern matcher returns either the\nstring \"failed\"\nor an extension of the given frame.\n\nThe basic idea of the matcher is to\ncheck the pattern against the data, element by element, accumulating\nbindings for the pattern variables.\n\nIf the pattern and the data\nobject are the same, the match succeeds and we return the frame of\nbindings accumulated so far.\n\nOtherwise, if the pattern is a variable\n(checked by the function is_variable declared in section)\nwe extend the current frame by binding the variable to the data, so\nlong as this is consistent with the bindings already in the frame.\n\nIf\nthe pattern and the data are both pairs, we (recursively) match the\nhead\nof the pattern against the\nhead\nof the data to produce a frame; in this frame we then match the\ntail\nof the pattern against the\ntail\nof the data.\n\nIf none of these cases are applicable, the match fails and\nwe return the\nstring \"failed\".", + "token_count": 274, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 6, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_6" }, { - "content": "An\nevaluator (or interpreter ) for a programming language is a\nfunction\nthat, when applied to\na statement or expression\nof the language, performs the actions required to evaluate that\nstatement or\nexpression.\n\nIt is no exaggeration to regard this as the most fundamental idea in\nprogramming:\nThe evaluator, which determines the meaning of\nstatements and\nexpressions in a programming language, is just another program.\n\nTo appreciate this point is to change our images of ourselves as\nprogrammers.\n\nWe come to see ourselves as designers of languages,\nrather than only users of languages designed by others.\n\nIn fact, we can regard almost any program as the evaluator for some\nlanguage.\n\nFor instance, the polynomial manipulation system of\nsection embodies the rules of\npolynomial arithmetic and implements them in terms of operations on\nlist-structured data.\n\nIf we augment this system with\nfunctions\nto read and print polynomial expressions, we have the core of a\nspecial-purpose language for dealing with problems in symbolic mathematics.\n\nThe digital-logic simulator of\nsection and the constraint\npropagator of section are legitimate\nlanguages in their own right, each with its own primitives, means of\ncombination, and means of abstraction.\n\nSeen from this perspective, the\ntechnology for coping with large-scale computer systems merges with the\ntechnology for building new computer languages, and\n\nWe now embark on a tour of the technology by which languages are\nestablished in terms of other languages.\n\nIn this chapter we shall use\nJavaScript\nas a base, implementing evaluators as\nJavaScript\nfunctions.\n\nWe will take the first step in understanding how languages are implemented\nby building an evaluator for\nJavaScript\nitself.\n\nThe language implemented by our evaluator will be a subset of\nJavaScript.", - "token_count": 279, - "has_code": false, + "content": "If none of these cases are applicable, the match fails and\nwe return the\nstring \"failed\".\n\nHere is the function that extends a frame by adding a new binding, if this is consistent with the bindings already in the frame:\n\n```javascript\nextend_if_consistent\n\tmake_binding\n\tappend_to_form_example_5\n\nfunction extend_if_consistent(variable, data, frame) {\n const binding = binding_in_frame(variable, frame);\n return is_undefined(binding)\n ? extend(variable, data, frame)\n : pattern_match(binding_value(binding), data, frame);\n}\n```\n\nIf there is no binding for the variable in the frame, we simply add\nthe binding of the variable to the data.\n\nOtherwise we match, in the\nframe, the data against the value of the variable in the frame.\n\nIf\nthe stored value contains only constants, as it must if it was stored\nduring pattern matching by\nextend_if_consistent,\nthen the match simply tests whether the stored and new values are the\nsame.\n\nIf so, it returns the unmodified frame; if not, it returns a failure\nindication.\n\nThe stored value may, however, contain pattern variables\nif it was stored during unification (see\nsection ).\n\nThe recursive match of the\nstored pattern against the new data will add or check bindings for the\nvariables in this pattern.\n\nFor example, suppose we have a frame in which\n$x\nis bound to\nlist(\"f\", $y)\nand\n$y\nis unbound, and we wish to augment this frame by a binding of\n$x\nto\nlist(\"f\", \"b\").\n\nWe look up\n$x\nand find that it is bound to\nlist(\"f\", $y).\n\nThis leads us to match\nlist(\"f\", $y)\nagainst the proposed new value\nlist(\"f\", \"b\")\nin the same frame.\n\nEventually this match extends the frame by adding a\nbinding of\n$y\nto\n\"b\".\n\nThe variable $x\nremains bound to\nlist(\"f\", $y).\n\nWe never modify a stored binding and we never store more than one binding\nfor a given variable.", + "token_count": 296, + "has_code": true, "chapter": "Metalinguistic Abstraction", - "section": null, - "subsection": null, - "chunk_index": 2, - "chunk_id": "Metalinguistic_Abstraction_Metalinguistic_Abstraction_2" + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 7, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_7" }, { - "content": "The language implemented by our evaluator will be a subset of\nJavaScript.\n\nAlthough the evaluator described in this chapter is written for a\nparticular\nsubset of JavaScript,\nit contains the essential structure of an evaluator for any\nlanguage\ndesigned for writing programs for a sequential machine.\n\n(In fact, most\nlanguage processors contain, deep within them, a little\nevaluator.)\nThe evaluator has been simplified for the purposes of illustration and\ndiscussion, and some features have been left out that would be\nimportant to include in a production-quality\nJavaScript\nsystem.\n\nNevertheless, this simple evaluator is adequate to execute most of\nthe programs in this book.\n\nAn important advantage of making the evaluator accessible as a\nJavaScript\nprogram is that we can implement alternative evaluation rules by describing\nthese as modifications to the evaluator program.\n\nOne place where we can use\nthis power to good effect is to gain extra control over the ways in which\ncomputational models embody the notion of time, which was so central to the\ndiscussion in chapter.\n\nThere, we mitigated some of the complexities\nof state and assignment by using streams to decouple the representation of\ntime in the world from time in the computer.\n\nOur stream programs, however,\nwere sometimes cumbersome, because they were constrained by the\napplicative-order evaluation of\nJavaScript.\n\nIn section , we ll change\nthe underlying language to provide for a more elegant approach, by modifying\nthe evaluator to provide for normal-order evaluation.\n\nSection implements a\nmore ambitious linguistic change, whereby statements and expressions\nhave many values, rather than just a single value.\n\nIn this language of\nnondeterministic computing , it is natural to express processes that\ngenerate all possible values for statements and expressions and then search\nfor those values that satisfy certain constraints.", - "token_count": 291, - "has_code": false, + "content": "We never modify a stored binding and we never store more than one binding\nfor a given variable.\n\nThe function apply_rules\nis the rule analog of\nfind_assertions\n(section ).\n\nIt takes as input a\npattern and a frame, and it forms a stream of extension frames by applying\nrules from the data base.\n\nThe function stream_flatmap\nmaps\napply_a_@rule\ndown the stream of possibly applicable rules (selected by\nfetch_rules,\nsection ) and combines the resulting\nstreams of frames.\n\n```javascript\napply_rules\n\tstream_flatmap\n\tapply_a_rule\n\tfetch_rules\n\tappend_to_form_example_5\n\nfunction apply_rules(pattern, frame) {\n return stream_flatmap(rule => apply_a_rule(rule, pattern, frame),\n fetch_rules(pattern, frame));\n}\n```\n\nThe function apply_a_rule applies a rule\nusing the method outlined in\nsection.\n\nIt first augments its\nargument frame by unifying the rule conclusion with the pattern in the\ngiven frame.\n\nIf this succeeds, it evaluates the rule body in this new\nframe.\n\nBefore any of this happens, however, the program renames all the variables\nin the rule with unique new names.\n\nThe reason for this is to prevent the\nvariables for different rule applications from becoming confused with each\nother.\n\nFor instance, if two rules both use a variable\nnamed$x,\nthen each one may add a binding for\n$x\nto the frame when it is applied.\n\nThese two\n$xs\nhave nothing to do with each other, and we should not be fooled into\nthinking that the two bindings must be consistent.\n\nRather than rename\nvariables, we could devise a more clever environment structure; however,\nthe renaming approach we have chosen here is the most straightforward,\neven if not the most efficient.\n\n(See\nexercise.) Here is the\napply_a_rule\nfunction:\n\n```javascript\napply_a_rule\n\trename_variables_in\n\tunify_match\n\tsingleton_stream\n\tis_rule\n\tappend_to_form_example_5\n\nfunction apply_a_rule(rule, query_pattern, query_frame) {\n const clean_rule = rename_variables_in(rule);\n const unify_result = unify_match(query_pattern,\n conclusion(clean_rule),\n query_frame);\n return unify_result === \"failed\"\n ? null\n : evaluate_query(rule_body(clean_rule),\n singleton_stream(unify_result));\n}\n```", + "token_count": 298, + "has_code": true, "chapter": "Metalinguistic Abstraction", - "section": null, - "subsection": null, - "chunk_index": 3, - "chunk_id": "Metalinguistic_Abstraction_Metalinguistic_Abstraction_3" + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 8, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_8" }, { - "content": "In this language of\nnondeterministic computing , it is natural to express processes that\ngenerate all possible values for statements and expressions and then search\nfor those values that satisfy certain constraints.\n\nIn terms of models of\ncomputation and time, this is like having time branch into a set of\npossible futures and then searching for appropriate time\nlines.\n\nWith our nondeterministic evaluator, keeping track of multiple values\nand performing searches are handled automatically by the underlying\nmechanism of the language.\n\nIn section we implement a\nlogic-programming language in which knowledge is expressed in terms\nof relations, rather than in terms of computations with inputs and outputs.\n\nEven though this makes the language drastically different from\nJavaScript,\nor indeed from any conventional language, we will see that\nthe logic-programming evaluator shares the essential structure of the\nJavaScript\nevaluator.", - "token_count": 138, - "has_code": false, + "content": "(See\nexercise.) Here is the\napply_a_rule\nfunction:\n\nWe generate unique variable names by associating a unique identifier\n(such as a number) with each rule application and combining this\nidentifier with the original variable names.\n\nFor example, if the\nrule-application identifier is 7, we might change each\n$x\nin the rule to\n$x_7\nand each\n$y\nin the rule to\n$y_7.\n\n(The functions make_new_variable\nand\nnew_rule_application_id\nare included with the syntax\nfunctions\nin section.)\n\n```javascript\nrename_variables_in\n\tis_variable_4\n\tappend_to_form_example_5\n\nfunction rename_variables_in(rule) {\n const rule_application_id = new_rule_application_id();\n function tree_walk(exp) {\n return is_variable(exp)\n ? make_new_variable(exp, rule_application_id)\n : is_pair(exp)\n ? pair(tree_walk(head(exp)),\n tree_walk(tail(exp)))\n : exp;\n }\n return tree_walk(rule);\n}\n```\n\nThe\nunification algorithm is implemented as a\nfunction\nthat takes as inputs two patterns and a frame and returns either the\nextended frame or the\nstring \"failed\".\n\nThe unifier is like the pattern matcher except that it is\nsymmetrical variables are allowed on both sides of the match.\n\nThe function unify_match\nis basically the same as\npattern_match,\nexcept that there is\nan extra clause\n(marked\n*** below) to handle\nthe case where the object on the right side of the match is a variable.\n\n```javascript\nunify_match\n\textend_if_possible\n\tvariable\n\tappend_to_form_example_5\n\nfunction unify_match(p1, p2, frame) {\n return frame === \"failed\"\n ? \"failed\"\n : equal(p1, p2)\n ? frame\n : is_variable(p1)\n ? extend_if_possible(p1, p2, frame)\n : is_variable(p2) // ***\n ? extend_if_possible(p2, p1, frame) // ***\n : is_pair(p1) && is_pair(p2)\n ? unify_match(tail(p1),\n tail(p2),\n unify_match(head(p1),\n head(p2),\n frame))\n : \"failed\";\n}\n```\n\nIn unification, as in one-sided pattern matching, we want to accept a\nproposed extension of the frame only if it is consistent with existing\nbindings.\n\nThe\nfunction\nextend_if_possible\nused in unification is the same as the\nfunction extend_if_consistent\nused in pattern matching except for two special checks, marked\n*** in the program below.", + "token_count": 292, + "has_code": true, "chapter": "Metalinguistic Abstraction", - "section": null, - "subsection": null, - "chunk_index": 4, - "chunk_id": "Metalinguistic_Abstraction_Metalinguistic_Abstraction_4" + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 9, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_9" }, { - "content": "Now that we have an evaluator expressed as a\nJavaScript\nprogram, we can experiment with alternative choices in\nJavaScript\nwith another member of the\nJavaScript\ncommunity, we can supply an evaluator that embodies\nthe change.\n\nThe recipient can then experiment with the new\nevaluator and send back comments as further modifications.\n\nNot only\ndoes the high-level implementation base make it easier to test and\ndebug the evaluator; in addition, the embedding enables the designer\nto snarf\nJavaScript\nevaluator uses primitives and control structure from the underlying\nJavaScript.\n\nOnly later (if ever) need the designer go to the trouble of building a\ncomplete implementation in a low-level language or in hardware.\n\nIn\nthis section and the next we explore some variations on\nJavaScript\nthat provide significant additional expressive power.", - "token_count": 128, + "content": "The\nfunction\nextend_if_possible\nused in unification is the same as the\nfunction extend_if_consistent\nused in pattern matching except for two special checks, marked\n*** in the program below.\n\nIf both parties to the match are unbound, we may bind either\nto the other.\n\nThe second check deals with attempts to bind a variable to a pattern\nthat includes that variable.\n\nSuch a situation can occur whenever a\nvariable is repeated in both patterns.\n\nConsider, for example,\nunifying the two patterns\nlist($x, $x)\nand\nlist($y, $\\langle$expression involving $y$\\rangle$)\nin a frame where both\n$x\nand\n$y\nare unbound.\n\nFirst\n$x\nis matched against\n$y,\nmaking a binding of\n$x\nto\n$y.\n\nNext, the same\n$x\nis matched against the given\nexpression involving\n$y.\n\nSince\n$x\nis already bound to\n$y,\nthis results in matching\n$y\nagainst the\nexpression.\n\nIf we think of the unifier as finding a set of\nvalues for the pattern variables that make the patterns the same, then\nthese patterns imply instructions to find a\n$y\nsuch that\n$y\nis equal to the expression involving\n$y.\n\nWe\nreject such\nbindings; these cases are recognized by the predicate\ndepends_on.\n\nOn the other hand, we do not want to reject attempts\nto bind a variable to itself.\n\nFor example, consider unifying\nlist($x, $x)\nand\nlist($y, $y).\n\nThe second attempt to bind\n$x\nto\n$y\nmatches\n$y\n(the stored value of $x\n)\nagainst\n$y\n(the new value of $x ).\n\nThis is taken care of by the\nequal\nclause of\nunify_match.", + "token_count": 251, "has_code": false, "chapter": "Metalinguistic Abstraction", - "section": "Lazy Evaluation", - "subsection": null, - "chunk_index": 1, - "chunk_id": "Metalinguistic_Abstraction_Lazy_Evaluation_1" + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 10, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_10" }, { - "content": "In this section we will implement a normal-order language that is\nthe same as\nJavaScript\nexcept that compound\nfunctions\nare non-strict in each argument.\n\nPrimitive\nfunctions\nwill still be strict.\n\nIt is not difficult to modify the evaluator of\nsection so that the language it\ninterprets behaves this way.\n\nAlmost all the required changes center around\nfunction\napplication.\n\nThe basic idea is that, when applying a\nfunction,\nthe interpreter must determine which arguments are to be evaluated and which\nare to be delayed.\n\nThe delayed arguments are not evaluated; instead, they\nare transformed into objects called\nthunk s.\nfunction\napplication is being evaluated.\n\nThe process of evaluating the expression in a thunk is called\nforcing.\nfunction\nthat will use the value of the thunk; when it is the value of a predicate of\na conditional; and when it is the value of\na function expression\nthat is about to be\napplied as a\nfunction.\n\nOne design choice we have available is whether or not to\nmemoize thunks, similar to the optimization for streams in\nsection.\n\nWith memoization, the first\ntime a thunk is forced, it stores the value that is computed.\n\nSubsequent\nforcings simply return the stored value without repeating the computation.\n\nWe ll make our interpreter memoize, because this is more efficient for\nmany applications.\n\nThere are tricky considerations here,\nhowever.\n\nThe main difference between the lazy evaluator and the one in section is in the handling of function applications in evaluate and\n\n```javascript\nThe is_application\n\tclause of\n```\n\n```javascript\neval_lazy_example\n functions_4_1_1_lazy\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_program = parse(\"1; { true; 3; }\");\nevaluate(my_program, the_empty_environment);\n```", - "token_count": 267, + "content": "This is taken care of by the\nequal\nclause of\nunify_match.\n\nThe function depends_on\nis a predicate that tests whether an expression proposed to be the value\nof a pattern variable depends on the variable.\n\nThis must be done relative\nto the current frame because the expression may contain occurrences of a\nvariable that already has a value that depends on our test variable.\n\nThe structure of\ndepends_on\nis a simple recursive tree walk in which we substitute for the values of\nvariables whenever necessary.\n\n```javascript\ndepends_on\n\tvariable\n\tmake_binding\n\tappend_to_form_example_5\n\nfunction depends_on(expression, variable, frame) {\n function tree_walk(e) {\n if (is_variable(e)) {\n if (equal(variable, e)) {\n return true;\n } else {\n const b = binding_in_frame(e, frame);\n return is_undefined(b)\n ? false\n : tree_walk(binding_value(b));\n }\n } else {\n return is_pair(e)\n ? tree_walk(head(e)) || tree_walk(tail(e))\n : false;\n }\n }\n return tree_walk(expression);\n}\n```\n\nOne important problem in designing logic programming languages is that\nof arranging things so that as few irrelevant\ndata-base entries as\npossible will be examined in checking a given pattern.\n\nFor this purpose,\nwe will represent an assertion as a list whose head is a string that\nrepresents the kind of information of the assertion.\n\nWe store the assertions in separate streams, one for each kind of information, in a table indexed by the kind.\n\nTo fetch an assertion that may match a pattern, we return (to be tested using the matcher) all the stored assertions that have the same head (the same kind of information).\n\nCleverer methods could also take advantage of information in the frame.\n\nWe avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria.\n\n```javascript\nfetch_assertions\n\tget_stream\n\tindex_key_of\n\tappend_to_form_example_5\n\nfunction fetch_assertions(pattern, frame) {\n return get_indexed_assertions(pattern);\n}\nfunction get_indexed_assertions(pattern) {\n return get_stream(index_key_of(pattern), \"assertion-stream\");\n}\n```", + "token_count": 297, "has_code": true, "chapter": "Metalinguistic Abstraction", - "section": "Lazy Evaluation", - "subsection": "An Interpreter with Lazy Evaluation", - "chunk_index": 1, - "chunk_id": "Metalinguistic_Abstraction_An_Interpreter_with_Lazy_Evaluation_1" + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 11, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_11" }, { - "content": "The main difference between the lazy evaluator and the one in section is in the handling of function applications in evaluate and\n\n```javascript\neval_lazy\n eval_lazy_example\n 3\n\n: is_application(component)\n? apply(actual_value(function_expression(component), env),\n arg_expressions(component), env)\n\nfunction evaluate(component, env) {\n return is_literal(component)\n ? literal_value(component)\n : is_name(component)\n ? lookup_symbol_value(symbol_of_name(component), env)\n : is_application(component)\n ? apply(actual_value(function_expression(component), env),\n arg_expressions(component), env)\n : is_operator_combination(component)\n ? evaluate(operator_combination_to_application(component), env)\n : is_conditional(component)\n ? eval_conditional(component, env)\n : is_lambda_expression(component)\n ? make_function(lambda_parameter_symbols(component),\n lambda_body(component), env)\n : is_sequence(component)\n ? eval_sequence(sequence_statements(component), env)\n : is_block(component)\n ? eval_block(component, env)\n : is_return_statement(component)\n ? eval_return_statement(component, env)\n : is_function_declaration(component)\n ? evaluate(function_decl_to_constant_decl(component), env)\n : is_declaration(component)\n ? eval_declaration(component, env)\n : is_assignment(component)\n ? eval_assignment(component, env)\n : error(component, \"unknown syntax -- evaluate\");\n}\n```\n\nThis is almost the same as the\nis_application\nclause of\nevaluate\nin section.\n\nFor lazy evaluation,\nhowever, we call\nargument\nexpressions, rather than the arguments produced by evaluating them.\n\nSince\nwe will need the environment to construct thunks if the arguments are to be\ndelayed, we must pass this as well.\n\nWe still evaluate the\nfunction expression,\nbecause\nfunction\nto be applied in order to dispatch on its type (primitive versus compound)\nand apply it.\n\nWhenever we need the actual value of an expression, we use\n\n```javascript\nactual_value_lazy_example\n functions_4_1_1_lazy\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nactual_value(parse(\"1 + 2;\"), the_global_environment);\n```\n\n```javascript\nactual_value_lazy\n actual_value_lazy_example\n 3\n\nfunction actual_value(exp, env) {\n return force_it(evaluate(exp, env));\n}\n```\n\ninstead of just evaluate, so that if the expression s value is a thunk, it will be forced.\n\nOur new version of.\n\nThe difference is that\nevaluate\nhas passed in unevaluated\nargument\nexpressions: For primitive\nfunctions\n(which are strict), we evaluate all the arguments before applying the\nprimitive; for compound\nfunctions\n(which are non-strict) we delay all the\narguments before applying the\nfunction.\n\n```javascript\napply_lazy_example\n functions_4_1_1_lazy\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst plus = list(\"primitive\", (x, y) => x + y);\napply(plus, list(list(\"literal\", 1), list(\"literal\", 2)), the_global_environment);\n```", - "token_count": 303, + "content": "We avoid building our criteria for indexing into the program; instead we call on predicates and selectors that embody our criteria.\n\n```javascript\nget_stream\n\toperation_table_from_chapter_3\n\toperation_table\n\tappend_to_form_example_5\n\nfunction get_stream(key1, key2) {\n const s = get(key1, key2);\n return is_undefined(s) ? null : s;\n}\n```\n\nRules are stored similarly, using the head of the rule conclusion.\n\nA pattern can match rules whose conclusions have the same head.\n\nThus, when fetching rules that might match a pattern we fetch all rules whose conclusions have the same head as the pattern.\n\n```javascript\nfetch_rules\n\tget_stream\n\tindex_key_of\n\tappend_to_form_example_5\n\nfunction fetch_rules(pattern, frame) {\n return get_indexed_rules(pattern);\n}\nfunction get_indexed_rules(pattern) {\n return get_stream(index_key_of(pattern), \"rule-stream\");\n}\n```\n\nThe function add_rule_or_assertion\nis used by\nquery_driver_loop\nto add assertions and rules to the data base.\n\nEach item is stored in the\nindex.\n\n```javascript\nadd_rule_or_assertion\n\tis_rule\n\tstore_assertion_in_index\n\tfetch_assertions\n\tfetch_rules\n\tappend_to_form_example_5\n\nfunction add_rule_or_assertion(assertion) {\n return is_rule(assertion)\n ? add_rule(assertion)\n : add_assertion(assertion);\n}\nfunction add_assertion(assertion) {\n store_assertion_in_index(assertion);\n return \"ok\";\n}\nfunction add_rule(rule) {\n store_rule_in_index(rule);\n return \"ok\";\n}\n```\n\nTo actually store an assertion or a rule, we store it in the appropriate stream.\n\n```javascript\nstore_assertion_in_index\n\toperation_table_from_chapter_3\n\toperation_table\n\tindex_key_of\n\tget_stream\n\tis_rule\n\tappend_to_form_example_5\n\nfunction store_assertion_in_index(assertion) {\n const key = index_key_of(assertion);\n const current_assertion_stream =\n get_stream(key, \"assertion-stream\");\n put(key, \"assertion-stream\",\n pair(assertion, () => current_assertion_stream));\n}\nfunction store_rule_in_index(rule) {\n const pattern = conclusion(rule);\n const key = index_key_of(pattern);\n const current_rule_stream =\n get_stream(key, \"rule-stream\");\n put(key, \"rule-stream\",\n pair(rule, () => current_rule_stream));\n}\n```\n\nThe key under which a pattern (an assertion or rule conclusion) is stored in the table is the string it starts with.\n\n```javascript\nindex_key_of\n\tvariable\n\tappend_to_form_example_5\n\nfunction index_key_of(pattern) { return head(pattern); }\n```\n\nThe query system uses a few stream operations that were not presented in chapter.\n\nThe functions stream_append_delayed\nand\ninterleave_delayed\nare just like\nstream_append\nand\n(section ),\nexcept that they take a delayed argument (like the\nfunction\nin section ).\n\nThis postpones looping in some cases (see\nexercise ).", + "token_count": 309, "has_code": true, "chapter": "Metalinguistic Abstraction", - "section": "Lazy Evaluation", - "subsection": "An Interpreter with Lazy Evaluation", - "chunk_index": 2, - "chunk_id": "Metalinguistic_Abstraction_An_Interpreter_with_Lazy_Evaluation_2" + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 12, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_12" }, { - "content": "The difference is that\nevaluate\nhas passed in unevaluated\nargument\nexpressions: For primitive\nfunctions\n(which are strict), we evaluate all the arguments before applying the\nprimitive; for compound\nfunctions\n(which are non-strict) we delay all the\narguments before applying the\nfunction.\n\n```javascript\napply_lazy\n list_of_arg_values\n apply_lazy_example\n 3\n\nfunction apply(fun, args, env) {\n if (is_primitive_function(fun)) {\n return apply_primitive_function(\n fun,\n list_of_arg_values(args, env)); // changed\n } else if (is_compound_function(fun)) {\n const result = evaluate(\n function_body(fun),\n extend_environment(\n function_parameters(fun),\n list_of_delayed_args(args, env), // changed\n function_environment(fun)));\n return is_return_value(result)\n ? return_value_content(result)\n : undefined;\n } else {\n error(fun, \"unknown function type -- apply\");\n }\n}\n```\n\nThe functions that process the arguments are just like list_of_values from section , except that list_of_delayed_args delays the arguments instead of evaluating them, and list_of_arg_values\n\nuses actual_value instead of evaluate:\n\n```javascript\nlist_of_arg_values\n\nfunction list_of_arg_values(exps, env) {\n return map(exp => actual_value(exp, env), exps);\n}\nfunction list_of_delayed_args(exps, env) {\n return map(exp => delay_it(exp, env), exps);\n}\n```\n\nThe other place we must change the evaluator is in the handling of conditionals, where we must use actual_value instead of to get the value\n\nof the predicate expression before testing whether it is true or false:\n\n```javascript\neval_if_lazy_example\n functions_4_1_1_lazy\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_cond_expr = parse(\"true ? 1 : 2;\");\neval_conditional(my_cond_expr, the_empty_environment);\n```\n\n```javascript\neval_if_lazy\n eval_if_lazy_example\n 1\n\nfunction eval_conditional(component, env) {\n return is_truthy(actual_value(conditional_predicate(component), env))\n ? evaluate(conditional_consequent(component), env)\n : evaluate(conditional_alternative(component), env);\n}\n```\n\nFinally, we must change the\ndriver_loop\nfunction\n(from section ) to use\nactual_@value\ninstead of\nso that if a delayed value is propagated back to the\nread-evaluate-print loop,\nit will be forced before being printed.\n\nWe also change the prompts to indicate that\nthis is the lazy evaluator:", - "token_count": 272, + "content": "This postpones looping in some cases (see\nexercise ).\n\nThe function stream_flatmap,\nwhich is used throughout the query evaluator to map a\nfunction\nover a stream of frames and combine the resulting streams of frames,\nis the stream analog of the\nfunction\nintroduced for ordinary lists in\nsection.\n\nUnlike ordinary\n, however, we accumulate the streams\nwith an interleaving process, rather than simply appending them (see\nexercises\nand ).\n\n```javascript\nstream_flatmap\n\tstream_append_delayed\n\tappend_to_form_example_5\n\nfunction stream_flatmap(fun, s) {\n return flatten_stream(stream_map(fun, s));\n}\nfunction flatten_stream(stream) {\n return is_null(stream)\n ? null\n : interleave_delayed(\n head(stream),\n () => flatten_stream(stream_tail(stream)));\n}\n```\n\nThe evaluator also uses the following simple function to generate a stream consisting of a single element:\n\n```javascript\nsingleton_stream\n\tappend_to_form_example_5\n\nfunction singleton_stream(x) {\n return pair(x, () => null);\n}\n```\n\nWe saw in section that\nthe driver loop first transforms an input string into the JavaScript\nsyntax representation.\n\nThe input is designed to look like a\nJavaScript expression so that we can use the\nparse function\nfrom section and\nalso to support JavaScript notation in\njavascript_predicate.\n\nFor example,\n\n```javascript\nparse('job($x, list(\"computer\", \"wizard\"));');\n```\n\nyields\n\n```javascript\nlist(\"application\",\n list(\"name\", \"job\"),\n list(list(\"name\", \"$x\"),\n list(\"application\",\n list(\"name\", \"list\"),\n list(list(\"literal\", \"computer\"),\n list(\"literal\", \"wizard\")))))\n```\n\nThe tag\n\"application\" indicates that\nsyntactically,\nthe query would be treated as a function application in JavaScipt.\n\nThe function\nunparse transforms the syntax\nback into a string:\n\n```javascript\nunparse(parse('job($x, list(\"computer\", \"wizard\"));'));\n\n'job($x, list(\"computer\", \"wizard\"))'\n```\n\nIn the query processor, we assumed a\nquery-language-specific\nrepresentation of assertions, rules, and queries.\n\nThe function\nconvert_@to_@query_@syntax\ntransforms the syntax representation into that representation.\n\nUsing the same example,\n\n```javascript\nconvert_to_query_syntax(parse('job($x, list(\"computer\", \"wizard\"));'));\n```\n\nyields\n\n```javascript\nlist(\"job\", list(\"name\", \"$x\"), list(\"computer\", \"wizard\"))\n```\n\nQuery-system functions such as\nadd_rule_or_assertion\nin section\nand\nevaluate_query\nin section\noperate on the query-language-specific representation using\nselectors and predicates such as\ntype ,\ncontents ,\nis_rule , and\nfirst_conjunct declared below.", + "token_count": 302, "has_code": true, "chapter": "Metalinguistic Abstraction", - "section": "Lazy Evaluation", - "subsection": "An Interpreter with Lazy Evaluation", - "chunk_index": 3, - "chunk_id": "Metalinguistic_Abstraction_An_Interpreter_with_Lazy_Evaluation_3" + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 13, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_13" }, { - "content": "We also change the prompts to indicate that\nthis is the lazy evaluator:\n\n```javascript\ndriver_loop_lazy\n functions_4_1_1_lazy\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n user_print\n user_read\n driver_loop_lazy_example\n\nconst input_prompt = \"L-evaluate input: \";\nconst output_prompt = \"L-evaluate value: \";\n\nfunction driver_loop(env) {\n const input = user_read(input_prompt);\n if (is_null(input)) {\n display(\"evaluator terminated\");\n } else {\n const program = parse(input);\n const locals = scan_out_declarations(program);\n const unassigneds = list_of_unassigned(locals);\n const program_env = extend_environment(locals, unassigneds, env);\n const output = actual_value(program, program_env);\n user_print(output_prompt, output);\n return driver_loop(program_env);\n }\n}\n\nconst input_prompt = \"L-evaluate input: \";\nconst output_prompt = \"L-evaluate value: \";\n\nfunction driver_loop(env) {\n const input = user_read(input_prompt);\n if (is_null(input)) {\n display(\"--- evaluator terminated ---\", \"\");\n } else {\n display(\"----------------------------\",\n input_prompt + \"\\n\" + input + \"\\n\");\n const program = parse(input);\n const locals = scan_out_declarations(program);\n const unassigneds = list_of_unassigned(locals);\n const program_env = extend_environment(locals, unassigneds, env);\n const output = actual_value(program, program_env);\n user_print(output_prompt, output);\n return driver_loop(program_env);\n }\n}\n```\n\nWith these changes made, we can start the evaluator and test it.\n\nThe\nsuccessful evaluation of the\ntry_me\nexpression\ndiscussed in section indicates\nthat the interpreter is performing lazy evaluation:\n\n```javascript\ndriver_loop_lazy_example\n driver_loop_lazy\n\nconst the_global_environment = setup_environment();\ndriver_loop(the_global_environment);\n\ndriver_loop(the_global_environment);\n\n// L-evaluate input:\n// function try_me(a, b) { return a === 0 ? 1 : b; }\n// L-evaluate value:\n// undefined\n\n// L-evaluate input:\n// try_me(0, head(null));\n// L-evaluate value:\n// 1\n```\n\n```javascript\nL-evaluate input:\n\nfunction try_me(a, b) {\n return a === 0 ? 1 : b;\n}\n```\n\n```javascript\nL-evaluate input:\n\n try_me(0, head(null));\n```\n\nOur evaluator must arrange to create thunks when\nfunctions\nare applied to arguments and to force these thunks later.\n\nA thunk must\npackage an expression together with the environment, so that the argument\ncan be produced later.\n\nTo force the thunk, we simply extract the expression\nand environment from the thunk and evaluate the expression in the\nenvironment.", - "token_count": 300, + "content": "Query-system functions such as\nadd_rule_or_assertion\nin section\nand\nevaluate_query\nin section\noperate on the query-language-specific representation using\nselectors and predicates such as\ntype ,\ncontents ,\nis_rule , and\nfirst_conjunct declared below.\n\nSyntax abstraction in the query system.\n\nThe predicate\nis_variable is used on the\nquery-language-specific representation during query processing and\non the JavaScript syntax representation during instantiation\nto identify names that start with a dollar sign.\n\nWe assume there is a function char_at that\nreturns a string containing only the character of the given string\nat the given position.\n\n```javascript\nis_variable_2\n\nfunction is_variable(exp) {\n return is_name(exp) && char_at(symbol_of_name(exp), 0) === \"$\";\n}\n\nconst is_variable = is_name;\n```\n\nUnique variables are constructed during rule application\n(in section ) by means of the\nfollowing functions.\n\nThe unique identifier for a rule application is a number, which is\nincremented each time a rule is applied.\n\n```javascript\nis_variable_4\n\nlet rule_counter = 0;\n\nfunction new_rule_application_id() {\n rule_counter = rule_counter + 1;\n return rule_counter;\n}\nfunction make_new_variable(variable, rule_application_id) {\n return make_name(symbol_of_name(variable) + \"_\" +\n stringify(rule_application_id));\n}\n```\n\nThe function convert_to_query_syntax\nrecursively\ntransforms the JavaScript syntax representation into\nthe query-language-specific representation by simplifying\nassertions, rules, and queries such that the symbol of a name in a\nfunction expression of an application becomes a tag, except that if\nthe symbol is \"pair\"\nor \"list\" , an (untagged) JavaScript pair\nor list is built.\n\nThis means that\nconvert_@to_@query_@syntax\ninterprets\napplications of\nthe constructors pair and\nlist during the transformation,\nand processing functions such as\npattern_match\nof section and\nunify_match\nof section\ncan operate directly on the intended pairs and\nlists rather than on the syntax representation generated by the parser.\n\nThe (one-element) argument list of\njavascript_predicate\nremains unprocessed, as explained below.\n\nA variable remains unchanged, and\na literal is simplified to the primitive value it contains.", + "token_count": 298, "has_code": true, "chapter": "Metalinguistic Abstraction", - "section": "Lazy Evaluation", - "subsection": "An Interpreter with Lazy Evaluation", - "chunk_index": 4, - "chunk_id": "Metalinguistic_Abstraction_An_Interpreter_with_Lazy_Evaluation_4" + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 14, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_14" }, { - "content": "To force the thunk, we simply extract the expression\nand environment from the thunk and evaluate the expression in the\nenvironment.\n\nWe use\nactual_value\nrather than\nso that in case the value of the expression is itself a thunk, we will force\nthat, and so on, until we reach something that is not a thunk:\n\n```javascript\nforce_it_lazy_v1\n\nfunction force_it(obj) {\n return is_thunk(obj)\n ? actual_value(thunk_exp(obj), thunk_env(obj))\n : obj;\n}\n```\n\nOne easy way to package an expression with an environment is to make a list\ncontaining the expression and the environment.\n\nThus, we create a thunk as\nfollows:\n\n```javascript\ndelay_it_lazy\n eval_lazy_example\n\nfunction delay_it(exp, env) {\n return list(\"thunk\", exp, env);\n}\nfunction is_thunk(obj) {\n return is_tagged_list(obj, \"thunk\");\n}\nfunction thunk_exp(thunk) { return head(tail(thunk)); }\n\nfunction thunk_env(thunk) { return head(tail(tail(thunk))); }\n```\n\nActually, what we want for our interpreter is not quite this, but\nrather thunks that have been memoized.\n\nWhen a thunk is forced, we will turn it into an evaluated thunk by replacing\nthe stored expression with its value and changing the\n\n```javascript\nforce_it_lazy\n eval_lazy_example\n\nfunction is_evaluated_thunk(obj) {\n return is_tagged_list(obj, \"evaluated_thunk\");\n}\nfunction thunk_value(evaluated_thunk) {\n return head(tail(evaluated_thunk));\n}\n\nfunction force_it(obj) {\n if (is_thunk(obj)) {\n const result = actual_value(thunk_exp(obj), thunk_env(obj));\n set_head(obj, \"evaluated_thunk\");\n set_head(tail(obj), result); // replace exp with its value\n set_tail(tail(obj), null); // forget unneeded env\n return result;\n } else if (is_evaluated_thunk(obj)) {\n return thunk_value(obj);\n } else {\n return obj;\n }\n}\n```\n\nNotice that the same delay_it function works both with and without memoization.", - "token_count": 243, + "content": "A variable remains unchanged, and\na literal is simplified to the primitive value it contains.\n\nAn exception to this processing is\njavascript_predicate.\n\nSince the instantiated JavaScript syntax\nrepresentation\nof its predicate expression is passed to\nevaluate\nof section ,\nthe original syntax representation coming from\nparse\nneeds to remain intact in the query-language-specific representation\nof the expression.\n\nIn this example\nof section\n\n```javascript\nand(salary($person, $amount), javascript_predicate($amount > 50000))\n```\n\nconvert_to_query_syntax produces a data structure in which a JavaScript syntax representation is embedded in a query-language-specific representation:\n\n```javascript\nlist(\"and\",\n list(\"salary\", list(\"name\", \"$person\"), list(\"name\", \"$amount\")),\n list(\"javascript_predicate\",\n list(\"binary_operator_combination\",\n \">\",\n list(\"name\", \"$amount\"),\n list(\"literal\", 50000))))\n```\n\nIn order to evaluate the javascript_predicate subexpression\nof that processed query, the function\njavascript_@predicate in\nsection calls the function\ninstantiate_@expression (below)\non the embedded JavaScript syntax\nrepresentation of $amount > 50000 to\nreplace the variable\nlist(\"name\", \"$amount\") by a literal,\nfor example list(\"literal\", 70000) , that represents\nthe primitive value to which\n$amount is bound, here 70000.\n\nThe JavaScript evaluator can evaluate the instantiated predicate, which now represents\n70000 > 50000.\n\nThe function javascript_predicate\nof section and the driver loop\nof section call\ninstantiate_@expression on an\nexpression to obtain a copy in which any variable in the\nexpression is replaced by its value in a given frame.\n\nThe input and result expressions use the JavaScript syntax\nrepresentation, so any value that results from instantiating a\nvariable needs to be converted from its form in the binding to\nthe JavaScript syntax representation.\n\n```javascript\ninstantiate\n\t make_binding\n\t variable\n\t express\n\t convert\n\t append_to_form_example_5\n\nfunction instantiate_expression(expression, frame) {\n return is_variable(expression)\n ? convert(instantiate_term(expression, frame))\n : is_pair(expression)\n ? pair(instantiate_expression(head(expression), frame),\n instantiate_expression(tail(expression), frame))\n : expression;\n}\n```", + "token_count": 269, "has_code": true, "chapter": "Metalinguistic Abstraction", - "section": "Lazy Evaluation", - "subsection": "An Interpreter with Lazy Evaluation", - "chunk_index": 5, - "chunk_id": "Metalinguistic_Abstraction_An_Interpreter_with_Lazy_Evaluation_5" + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 15, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_15" }, { - "content": "In section , where we began\nour discussion of models of evaluation, we noted that\nJavaScript\nis an applicative-order language, namely, that all the arguments to\nJavaScript\nfunctions\nare evaluated when the\nfunction\nis applied.\n\nIn contrast, normal-order languages delay evaluation of\nfunction\narguments until the actual argument values are needed.\n\nDelaying evaluation of\nfunction\narguments until the last possible moment (e.g., until they are required by a\nprimitive operation) is called\nlazy evaluation.\nfunction\n\n```javascript\ntry_me_example\n\ntry_me(0, head(null));\n```\n\n```javascript\ntry_me\n try_me_example\n 1\n\nfunction try_me(a, b) {\n return a === 0 ? 1 : b;\n}\n\n// Source Academy opens this program\n// in lazy mode. Choose \"Source §2\" to\n// to compare with strict mode\nfunction try_me(a, b) {\n return a === 0 ? 1 : b;\n}\n```\n\nEvaluating\n\n```javascript\ntry_me(0, head(null));\n\tsignals\n```\n\nan error in\nJavaScript.\n\nWith lazy evaluation, there would be no error.\n\nEvaluating the\nstatement\nwould return 1, because the argument\nhead(null)\nwould never be evaluated.\n\nAn example that exploits lazy evaluation is the declaration of a function\n\n```javascript\nunless\n unless_example\n\nfunction unless(condition, usual_value, exceptional_value) {\n return condition ? exceptional_value : usual_value;\n}\n\n// Source Academy opens this program\n// in lazy mode. Choose \"Source §2\" to\n// to compare with strict mode\nfunction unless(condition, usual_value, exceptional_value) {\n return condition ? exceptional_value : usual_value;\n}\n```\n\nthat can be used in statements such as\n\n```javascript\nxs_is_null\n\nconst xs = null;\n```\n\n```javascript\nunless_example\n unless\n xs_is_null\n\nunless(is_null(xs), head(xs), display(\"error: xs should not be null\"));\n```\n\nThis won t work in an applicative-order language because both the\nusual value and the exceptional value will be evaluated before\n).\n\nAn advantage of lazy evaluation is\nthat some\nfunctions,\nsuch as\n\nIf the body of a\nfunction\nis entered before an argument has been evaluated we say that the\nfunction\nis\nnon-strict in that argument.", - "token_count": 308, + "content": "The input and result expressions use the JavaScript syntax\nrepresentation, so any value that results from instantiating a\nvariable needs to be converted from its form in the binding to\nthe JavaScript syntax representation.\n\nWhen the process encounters a pair, a new pair is constructed\nwhose parts are the instantiated versions of the original parts.\n\nFor example, if\n$x\nis bound to the pair\n$[\\texttt{\\$y}, 5]$\nin a frame $f$\nas the result of unification, and\n$y\nis in turn bound to 3,\nthe result of applying\ninstantiate_term to\nlist(\"name\", \"$x\") and\n$f$ is the pair\n$[3, 5]$.\n\n```javascript\nexpress\n\nfunction instantiate_term(term, frame) {\n if (is_variable(term)) {\n const binding = binding_in_frame(term, frame);\n return is_undefined(binding)\n ? term // leave unbound variable as is\n : instantiate_term(binding_value(binding), frame);\n } else if (is_pair(term)) {\n return pair(instantiate_term(head(term), frame),\n instantiate_term(tail(term), frame));\n } else { // $\\texttt{term}$ is a primitive value\n return term;\n }\n}\n```\n\nThe function\nconvert\nconstructs a JavaScript syntax representation for a variable,\npair, or primitive value returned by\ninstantiate_term.\n\nA pair in the original becomes an application of JavaScript's\npair constructor and a primitive value becomes a literal.\n\n```javascript\nconvert\n\nfunction convert(term) {\n return is_variable(term)\n ? term\n : is_pair(term)\n ? make_application(make_name(\"pair\"),\n list(convert(head(term)),\n convert(tail(term))))\n : // $\\texttt{term}$ is a primitive value\n make_literal(term);\n}\n```\n\n```javascript\nappend_to_form_example_5\n\t append_to_form\n\nprocess_query(`assert(\nrule(append_to_form(null, $y, $y)))`);\nprocess_query(`assert(\nrule(append_to_form(pair($u, $v), $y, pair($u, $z)),\n append_to_form($v, $y, $z)))`);\n\nprocess_query(`append_to_form($x, $y, list(\"a\", \"b\", \"c\", \"d\"))`);\n```\n\nTo illustrate these three functions, consider what happens when the query\n\n```javascript\njob($x, list(\"computer\", \"wizard\"))\n```\n\nwhose JavaScript syntax representation is given\nat the beginning of section , is\nprocessed by the driver loop.\n\nLet's say a frame $g$ of the result stream\nbinds the variable\n$x to the pair\n$[\\texttt{\"Bitdiddle\"}, \\texttt{\\$y}]$ and\nthe variable $y to the pair\n$[\\texttt{\"Ben\"}, \\texttt{null}]$.\n\nThen\n\n```javascript\ninstantiate_term(list(\"name\", \"$\\$$x\"), $g$)\n```\n\nreturns the list\n\n```javascript\nlist(\"Bitdiddle\", \"Ben\")\n```\n\nwhich convert transforms into\n\n```javascript\nlist(\"application\",\n list(\"name\", \"pair\"),\n list(list(\"literal\", \"Bitdiddle\"),\n list(\"application\",\n list(\"name\", \"pair\"),\n list(list(\"literal\", \"Ben\"),\n list(\"literal\", null)))))\n```", + "token_count": 326, "has_code": true, "chapter": "Metalinguistic Abstraction", - "section": "Lazy Evaluation", - "subsection": "Normal Order and Applicative Order", - "chunk_index": 1, - "chunk_id": "Metalinguistic_Abstraction_Normal_Order_and_Applicative_Order_1" + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 16, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_16" }, { - "content": "If the body of a\nfunction\nis entered before an argument has been evaluated we say that the\nfunction\nis\nnon-strict in that argument.\n\nIf the argument is evaluated before\nthe body of the\nfunction\nis entered we say that the\nfunction\nis\nstrict in that\nargument.\nfunctions\nare strict in each argument.\n\nIn a purely normal-order language, all compound\nfunctions\nare non-strict in each argument, and primitive\nfunctions\nmay be either strict or non-strict.\n\nThere are also languages (see\nexercise ) that give\nprogrammers detailed control over the strictness of the\nfunctions\nthey define.\n\nA striking example of a\nfunction\nthat can usefully be made non-strict is\npair\n(or, in general, almost any constructor for data structures).\n\nOne can do useful computation, combining elements to form\ndata structures and operating on the resulting data structures,\neven if the values of the elements are not known.\n\nIt makes perfect\nsense, for instance, to compute the length of a list without knowing\nthe values of the individual elements in the list.\n\nWe will exploit\nthis idea in section to implement the\nstreams of chapter as lists formed of non-strict\npairs.", - "token_count": 189, - "has_code": false, + "content": "which convert transforms into\n\n```javascript\nlist(\"application\",\n list(\"name\", \"job\"),\n list(list(\"application\",\n list(\"name\", \"pair\"),\n list(list(\"literal\", \"Bitdiddle\"),\n list(\"application\",\n list(\"name\", \"pair\"),\n list(list(\"literal\", \"Ben\"),\n list(\"literal\", null))))),\n list(\"application\",\n list(\"name\", \"list\"),\n list(list(\"literal\", \"computer\"),\n list(\"literal\", \"wizard\")))))\n```\n\nThe driver loop unparses this representation and displays it as:\n\n```javascript\n'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'\n```\n\nThe function unparse\ntransforms a component given in the JavaScript syntax representation\ninto a string by applying the syntax rules\nof section.\n\nWe describe unparse only for\nthose kinds of expressions that appear in the examples of\nsection , leaving statements\nand the remaining kinds of expressions\nas exercise.\n\nA literal is transformed by\nstringify ing its value, and\na name is transformed into its\nsymbol.\n\nAn application is formatted by unparsing the function expression,\nwhich we can assume to be a name here, followed by the comma-separated\nargument expression strings enclosed in parentheses.\n\nBinary operator combinations are formatted using infix notation.", + "token_count": 146, + "has_code": true, "chapter": "Metalinguistic Abstraction", - "section": "Lazy Evaluation", - "subsection": "Normal Order and Applicative Order", - "chunk_index": 2, - "chunk_id": "Metalinguistic_Abstraction_Normal_Order_and_Applicative_Order_2" + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 17, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_17" }, { - "content": "In section , we showed how to\nimplement streams as delayed lists.\n\nWe used a\nto construct a\npromise to compute the\ntail\nof a stream, without actually fulfilling that promise until later.\n\n```javascript\nWe were forced to create streams as a new kind of data object similar\n\tbut not identical to lists, and this required us to reimplement many\n\tordinary list operations (\n```\n\nWith lazy evaluation, streams and lists can be identical, so there is\nno need for\nseparate list and stream operations.\n\nAll we need to do is to arrange matters\nso that\npair\nis non-strict.\n\nOne way to accomplish this is to extend the lazy evaluator\nto allow for non-strict primitives, and to implement\npair\nas one of these.\n\nAn easier way is to recall\n(section ) that there is no fundamental need\nto implement\npair\nas a primitive at all.\n\nInstead, we can represent\nfunctions :\n\n```javascript\npair_lazy_header\n\nconst my_pair_lazy = `\n```\n\n```javascript\npair_lazy_footer\n\nhead(tail(pair(1, pair(3, 2))));\n`;\n```\n\n```javascript\npair_lazy_example\n parse_and_evaluate_lazy\n pair_lazy_header\n pair_lazy\n pair_lazy_footer\n 3\n\nparse_and_evaluate(my_pair_lazy);\n```\n\n```javascript\npair_lazy\n\nfunction pair(x, y) {\n return m => m(x, y);\n}\nfunction head(z) {\n return z((p, q) => p);\n}\nfunction tail(z) {\n return z((p, q) => q);\n}\n```\n\nIn terms of these basic operations, the standard definitions of the list\noperations will work with infinite lists (streams) as well as finite ones,\nand the stream operations can be implemented as list operations.\n\nHere are\nsome examples:\n\n```javascript\nlist_lib_test_header\n\nconst my_list_lib_test = `\n```\n\n```javascript\nlist_lib_test_footer\n\nlist_ref(integers, 17);\n`;\n```\n\n```javascript\nlist_lib_test\n parse_and_evaluate_lazy\n list_lib_test_header\n pair_lazy\n list_library_lazy\n list_lib_test_header\n list_lib_test_footer\n 18\n\nparse_and_evaluate(my_list_lib_test);\n```", - "token_count": 267, + "content": "Binary operator combinations are formatted using infix notation.\n\n```javascript\ncomma_separated\n\nfunction comma_separated(strings) {\n return accumulate((s, acc) => s + (acc === \"\" ? \"\" : \", \" + acc),\n \"\",\n strings);\n}\n```\n\nThe function unparse would work fine without the clause\n\n```javascript\n: is_list_construction(exp)\n? unparse(make_application(make_name(\"list\"),\n element_expressions(exp)))\n```\n\nbut the output string would be unnecessarily\nverbose in cases where pattern variables are instantiated\nby lists.\n\nIn the example above, where processing the query\n\n```javascript\njob($x, list(\"computer\", \"wizard\"))\n```\n\nyields a frame that binds $x to $[\\texttt{\"Bitdiddle\"}, [\\texttt{\"Ben\"}, \\texttt{null}]]$ , unparse produces\n\n```javascript\n'job(list(\"Bitdiddle\", \"Ben\"), list(\"computer\", \"wizard\"))'\n```\n\nHowever, without the clause it would produce\n\n```javascript\n'job(pair(\"Bitdiddle\", pair(\"Ben\", null)), list(\"computer\", \"wizard\"))'\n```\n\nwhich explicitly constructs the two pairs that make up the first list.\n\nTo achieve the more concise formatting used throughout\nsection ,\nwe inserted the clause to check\nif the expression constructs a list, in which case\nwe format it as a single application of\nlist to the list of element\nexpressions that we extract from the expression.\n\nA list construction is the literal\nnull or\nan application of\npair\nwhose second argument is itself a list construction.\n\n```javascript\nis_list_construction\n\nfunction is_list_construction(exp) {\n return (is_literal(exp) && is_null(literal_value(exp))) ||\n (is_application(exp) && is_name(function_expression(exp)) &&\n symbol_of_name(function_expression(exp)) === \"pair\" &&\n is_list_construction(head(tail(arg_expressions(exp)))));\n}\n```\n\nExtracting the element expressions from a given list construction amounts to collecting the first arguments of applications of pair until the literal null is reached.\n\n```javascript\nelement_expressions\n\nfunction element_expressions(list_constr) {\n return is_literal(list_constr)\n ? null // $\\texttt{list\\char`_constr}$ is literal $\\texttt{null}$\n : // $\\texttt{list\\char`_constr}$ is application of $\\texttt{pair}$\n pair(head(arg_expressions(list_constr)),\n element_expressions(\n head(tail(arg_expressions(list_constr)))));\n}\n\nfunction element_expressions(list_constr) {\n return is_literal(list_constr)\n ? null // list_constr is literal null\n : // list_constr is application of pair\n pair(head(arg_expressions(list_constr)),\n element_expressions(\n head(tail(arg_expressions(list_constr)))));\n}\n```\n\nThe functions type\nand , used by\nevaluate_query\n(section ), specify that a\nsyntactic form of a query-language-specific representation\nis identified by\nthe string in its head.", + "token_count": 312, "has_code": true, "chapter": "Metalinguistic Abstraction", - "section": "Lazy Evaluation", - "subsection": "Streams as Lazy Lists", - "chunk_index": 1, - "chunk_id": "Metalinguistic_Abstraction_Streams_as_Lazy_Lists_1" + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 18, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_18" }, { - "content": "Here are\nsome examples:\n\n```javascript\nlist_library_lazy\n\nfunction list_ref(items, n) {\n return n === 0\n ? head(items)\n : list_ref(tail(items), n - 1);\n}\nfunction map(fun, items) {\n return is_null(items)\n ? null\n : pair(fun(head(items)),\n map(fun, tail(items)));\n}\nfunction scale_list(items, factor) {\n return map(x => x * factor, items);\n}\nfunction add_lists(list1, list2) {\n return is_null(list1)\n ? list2\n : is_null(list2)\n ? list1\n : pair(head(list1) + head(list2),\n add_lists(tail(list1), tail(list2)));\n}\nconst ones = pair(1, ones);\nconst integers = pair(1, add_lists(ones, integers));\n```\n\n```javascript\nL-evaluate input:\n\nlist_ref(integers, 17);\n```\n\nNote that these lazy lists are even lazier than the streams of\nchapter : The\nhead\nof the list, as well as the\ntail,\nis delayed.\nhead\nor\ntail\nof a lazy pair need not force the value of a list element.\n\nThe value will be\nforced only when it is really needed e.g., for use as the argument\nof a primitive, or to be printed as an answer.\n\nLazy pairs also help with the problem that arose with streams in\nsection , where we\nfound that formulating stream models of systems with loops may require us to\nsprinkle our programs with\nadditional lambda expressions for\nWith lazy evaluation, all arguments to\nfunctions\nare delayed uniformly.\n\nFor instance, we can implement\nfunctions\nto integrate lists and solve differential equations as we originally\nintended in section :\n\n```javascript\nlazy_integral_header\n\nconst my_integral = `\n```\n\n```javascript\nlazy_integral_footer\n\nlist_ref(solve(x => x, 1, 0.1), 10);\n`;\n```\n\n```javascript\nlazy_integral_test\n parse_and_evaluate_lazy\n lazy_integral_header\n pair_lazy\n list_library_lazy\n lazy_integral\n lazy_integral_footer\n 2.5937424601\n\nparse_and_evaluate(my_integral);\n```\n\n```javascript\nlazy_integral\n\nfunction integral(integrand, initial_value, dt) {\n const int = pair(initial_value,\n add_lists(scale_list(integrand, dt),\n int));\n return int;\n}\nfunction solve(f, y0, dt) {\n const y = integral(dy, y0, dt);\n const dy = map(f, y);\n return y;\n}\n```\n\n```javascript\nL-evaluate input:\n\nlist_ref(solve(x => x, 1, 0.001), 1000);\n```", - "token_count": 295, + "content": "The functions type\nand , used by\nevaluate_query\n(section ), specify that a\nsyntactic form of a query-language-specific representation\nis identified by\nthe string in its head.\n\n```javascript\ntype\n\tappend_to_form_example_5\n\tfunctions_4_1_2\n\nfunction type(exp) {\n return is_pair(exp)\n ? head(exp)\n : error(exp, \"unknown expression type\");\n}\nfunction contents(exp) {\n return is_pair(exp)\n ? tail(exp)\n : error(exp, \"unknown expression contents\");\n}\n```\n\nThe following functions, used by query_driver_loop (in section ), specify that rules and assertions are added to the data base by an assert command, which\n\nthe function convert_to_query_syntax transforms into a pair of the form [\"assert\", rule-or-assertion ] :\n\n```javascript\nis_assertion\n\ttype\n\tappend_to_form_example_5\n\nfunction is_assertion(exp) {\n return type(exp) === \"assert\";\n}\nfunction assertion_body(exp) { return head(contents(exp)); }\n```\n\nHere are the declarations of the predicates and selectors for the , , , and javascript_predicate syntactic forms (section ):\n\n```javascript\nis_empty_conjunction\n\tappend_to_form_example_5\n\nfunction is_empty_conjunction(exps) { return is_null(exps); }\n\nfunction first_conjunct(exps) { return head(exps); }\n\nfunction rest_conjuncts(exps) { return tail(exps); }\n\nfunction is_empty_disjunction(exps) { return is_null(exps); }\n\nfunction first_disjunct(exps) { return head(exps); }\n\nfunction rest_disjuncts(exps) { return tail(exps); }\n\nfunction negated_query(exps) { return head(exps); }\n\nfunction javascript_predicate_expression(exps) { return head(exps); }\n```\n\nThe following three functions define the query-language-specific representation of rules:\n\n```javascript\nis_rule\n\tfunctions_4_1_2\n\tappend_to_form_example_5\n\nfunction is_rule(assertion) {\n return is_tagged_list(assertion, \"rule\");\n}\nfunction conclusion(rule) { return head(tail(rule)); }\n\nfunction rule_body(rule) {\n return is_null(tail(tail(rule)))\n ? list(\"always_true\")\n : head(tail(tail(rule)));\n}\n```\n\nFrames are represented as lists of bindings, which are variable-value pairs:\n\n```javascript\nmake_binding\n\toperation_table_from_chapter_3\n\toperation_table\n\tappend_to_form_example_5\n\nfunction make_binding(variable, value) {\n return pair(variable, value);\n}\nfunction binding_variable(binding) {\n return head(binding);\n}\nfunction binding_value(binding) {\n return tail(binding);\n}\nfunction binding_in_frame(variable, frame) {\n return assoc(variable, frame);\n}\nfunction extend(variable, value, frame) {\n return pair(make_binding(variable, value), frame);\n}\n```", + "token_count": 281, "has_code": true, "chapter": "Metalinguistic Abstraction", - "section": "Lazy Evaluation", - "subsection": "Streams as Lazy Lists", - "chunk_index": 2, - "chunk_id": "Metalinguistic_Abstraction_Streams_as_Lazy_Lists_2" + "section": "Logic Programming", + "subsection": "Implementing the Query System", + "chunk_index": 19, + "chunk_id": "Metalinguistic_Abstraction_Implementing_the_Query_System_19" }, { - "content": "In chapter we stressed that computer science deals with\n\nMost programming languages, including\nJavaScript,\nare organized around\ncomputing the values of mathematical functions.\n\nExpression-oriented\nlanguages\n(such as , C, Python, and JavaScript)\ncapitalize on the\npun that an expression that describes the value of a\nfunction may also be interpreted as a means of computing that value.\n\nBecause of this, most programming languages are strongly biased toward\nunidirectional computations (computations with well-defined inputs and\noutputs).\n\nThere are, however, radically different programming languages\nthat relax this bias.\n\nWe saw one such example in\nsection , where the objects of\ncomputation were arithmetic constraints.\n\nIn a constraint system the\ndirection and the order of computation are not so well specified; in\ncarrying out a computation the system must therefore provide more detailed\nhow to knowledge than would be the case with an ordinary\narithmetic computation.\n\nThis does not mean, however, that the user is\nreleased altogether from the responsibility of providing imperative\nknowledge.\n\nThere are many constraint networks that implement the same set\nof constraints, and the user must choose from the set of mathematically\nequivalent networks a suitable network to specify a particular computation.\n\nThe nondeterministic program evaluator of\nsection also moves\naway from the view that programming is about constructing algorithms for\ncomputing unidirectional functions.\n\nIn a nondeterministic language,\nexpressions can have more than one value, and, as a result, the\ncomputation is\ndealing with\nunification.\n\nThis approach, when it works, can be a very\nwhat is\nfact can be used to solve a number of different problems that would have\ndifferent how to components.\n\nAs an example, consider the\nJavaScript,\nwe could define\npair,\nas we did in section :\n\n```javascript\nfunction append(x, y) {\n return is_null(x)\n ? y\n : pair(head(x), append(tail(x), y));\n}\n```", - "token_count": 296, + "content": "The means of combination used in the query language may at first seem\nidentical to the operations ,\n, and of\nmathematical logic, and the application of query-language rules is in\nfact accomplished through a legitimate method of\ninference.\n\nThis identification of the query language with\nmathematical logic is not really valid, though, because the query language\nprovides a\ncontrol structure that interprets the logical statements\nprocedurally.\n\nWe can often take advantage of this control structure.\n\nFor example, to find all of the supervisors of programmers we could\nformulate a query in either of two logically equivalent forms:\n\n```javascript\nand(job($x, list(\"computer\", \"programmer\")),\n supervisor($x, $y))\n```\n\nor\n\n```javascript\nand(supervisor($x, $y),\n job($x, list(\"computer\", \"programmer\")))\n```\n\nIf a company has many more supervisors than programmers, it is better to use the first form rather than the second, because the data base\n\nmust be scanned for each intermediate result (frame) produced by the first clause of the.\n\nThe aim of logic programming is to provide the programmer with\ntechniques for decomposing a computational problem into two separate\nproblems:\nwhat is to be computed, and how this\nshould be computed.\n\nThis is accomplished by selecting a subset of the\nstatements of mathematical logic that is powerful enough to be able to\ndescribe anything one might want to compute, yet weak enough to have a\ncontrollable procedural interpretation.\n\nThe intention here is that,\non the one hand, a program specified in a logic programming language\nshould be an effective program that can be carried out by a computer.\n\nControl ( how to compute) is effected by using the order of\nevaluation of the language.\n\nWe should be able to arrange the order of\nclauses and the order of subgoals within each clause so that the\ncomputation is done in an order deemed to be effective and efficient.", + "token_count": 301, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Logic Programming", - "subsection": null, + "subsection": "Is Logic Programming Mathematical Logic?", "chunk_index": 1, - "chunk_id": "Metalinguistic_Abstraction_Logic_Programming_1" + "chunk_id": "Metalinguistic_Abstraction_Is_Logic_Programming_Mathematical_Logic_1" }, { - "content": "As an example, consider the\nJavaScript,\nwe could define\npair,\nas we did in section :\n\nThis\nfunction\ncan be regarded as a translation into\nJavaScript\nof the following two rules, the first of which covers the case where the\nfirst list is empty and the second of which handles the case of a nonempty\nlist, which is a\npair\nof two parts:\n-\n-\nFor any list\n-\n-\nFor any\npair(u, v)\nand\npair(u, z)\nif\nUsing the\nfunction,\nwe can answer questions such as\nFind the\nlist(\"a\", \"b\")\nand\nlist(\"c\", \"d\").\n\nBut the same two rules are also sufficient for answering the following\nsorts of questions, which the\nfunction\ncan t answer:\nFind a list\nlist(\"a\", \"b\")\nto produce\nlist(\"a\", \"b\", \"c\", \"d\").\n\nFind all\nlist(\"a\", \"b\", \"c\", \"d\").\n\nIn a\nfunction\nby stating the two rules about How to knowledge is provided automatically by the\ninterpreter to allow this single pair of rules to be used to answer all\nthree types of questions about\n\nContemporary logic programming languages (including the one we\nimplement here) have substantial deficiencies, in that their general\nhow to methods can lead them into spurious infinite loops or\nother undesirable behavior.\n\nLogic programming is an active field of research\nin computer science.\n\nEarlier in this chapter we explored the technology of implementing\ninterpreters and described the elements that are essential to an\ninterpreter for a\nJavaScript-like\nlanguage (indeed, to an interpreter for any conventional language).\n\nNow we\nwill apply these ideas to discuss an interpreter for a logic programming\nlanguage.\n\nWe call this\nlanguage the\nquery language , because it is very useful for\nretrieving information from data bases by formulating\nqueries , or questions, expressed in the language.", - "token_count": 287, - "has_code": false, + "content": "We should be able to arrange the order of\nclauses and the order of subgoals within each clause so that the\ncomputation is done in an order deemed to be effective and efficient.\n\nOur query language can be regarded as just such a procedurally\ninterpretable subset of mathematical logic.\n\nAn assertion represents a\nsimple fact (an atomic proposition).\n\nA rule represents the\nimplication that the rule conclusion holds for those cases where the\nrule body holds.\n\nA rule has a natural procedural interpretation: To\nestablish the conclusion of the rule, establish the body of the rule.\n\nRules, therefore, specify computations.\n\nHowever, because rules can\nalso be regarded as statements of mathematical logic, we can justify any\ninference accomplished by a logic program by asserting that\nthe same result could be obtained by working entirely within\nmathematical logic.\n\nA consequence of the procedural interpretation of logic programs is\nthat it is possible to construct hopelessly inefficient programs for\nsolving certain problems.\n\nAn extreme case of inefficiency occurs when\nthe system falls into infinite loops in making deductions.\n\nAs a\nsimple example, suppose we are setting up a data base of famous\nmarriages, including\n\n```javascript\nassert(married(\"Minnie\", \"Mickey\"))\n```\n\nIf we now ask\n\n```javascript\nmarried(\"Mickey\", $who)\n```\n\nwe will get no response, because the system doesn t know that if\n$A$ is married to $B$ ,\nthen $B$ is married to\n$A$.\n\nSo we assert the rule\n\n```javascript\nassert(rule(married($x, $y),\n married($y, $x)))\n```\n\nand again query\n\n```javascript\nmarried(\"Mickey\", $who)\n```\n\nUnfortunately, this will drive the system into an infinite loop, as\nfollows:\n-\n-\nThe system finds that the rule is applicable; that is, the rule conclusion married($x, $y) unifies with the query pattern married(\"Mickey\", $who) to produce a frame in which\n$x\nis bound to\n\"Mickey\"\nand\n$y\nis bound to\n$who.", + "token_count": 300, + "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Logic Programming", - "subsection": null, + "subsection": "Is Logic Programming Mathematical Logic?", "chunk_index": 2, - "chunk_id": "Metalinguistic_Abstraction_Logic_Programming_2" + "chunk_id": "Metalinguistic_Abstraction_Is_Logic_Programming_Mathematical_Logic_2" }, { - "content": "We call this\nlanguage the\nquery language , because it is very useful for\nretrieving information from data bases by formulating\nqueries , or questions, expressed in the language.\n\nEven though the\nquery language is very different from\nJavaScript,\nwe will find it convenient to describe the language in terms of the same\ngeneral framework we have been using all along: as a collection of primitive\nelements, together with means of combination that enable us to combine\nsimple elements to create more complex elements and means of abstraction\nthat enable us to regard complex elements as single conceptual units.\n\nAn\ninterpreter for a logic programming language is considerably more complex\nthan an interpreter for a language like\nJavaScript.\n\nNevertheless, we will see\nthat our.\n\nIn\nparticular, there will be an evaluate part that classifies\nexpressions according to type and an apply part that\nimplements the language s abstraction mechanism\n(functions\nin the case of\nJavaScript,\nand rules in the case of logic programming).\n\nAlso, a central role\nis played in the implementation by a frame data structure, which determines\nthe correspondence between symbols and their associated values.\n\nOne\nadditional interesting aspect of our query-language implementation is\nthat we make substantial use of streams, which were introduced in\nchapter.", - "token_count": 208, - "has_code": false, + "content": "Unfortunately, this will drive the system into an infinite loop, as\nfollows:\n-\n-\nThe system finds that the rule is applicable; that is, the rule conclusion married($x, $y) unifies with the query pattern married(\"Mickey\", $who) to produce a frame in which\n$x\nis bound to\n\"Mickey\"\nand\n$y\nis bound to\n$who.\n\nThe system is now in an infinite loop.\n\nIndeed, whether the system\nwill find the simple answer\nmarried(\"Minnie\", \"Mickey\")\nbefore it goes into the loop depends on implementation details concerning the\norder in which the system checks the items in the data base.\n\nThis is a very\nsimple example of the kinds of loops that can occur.\n\nCollections of\ninterrelated rules can lead to loops that are much harder to anticipate, and\nthe appearance of a loop can depend on the order of clauses in an\n(see\nexercise ) or on low-level details\nconcerning the order in which the system processes queries.\n\nAnother quirk in the query system concerns.\n\nGiven the data base of\nsection , consider the\nfollowing two queries:\n\n```javascript\nand(supervisor($x, $y),\n not(job($x, list(\"computer\", \"programmer\"))))\n\nand(not(job($x, list(\"computer\", \"programmer\"))),\n supervisor($x, $y))\n```\n\nThese two queries do not produce the same result.\n\nThe first query\nbegins by finding all entries in the data base that match\nsupervisor($x, $y),\nand then filters the resulting frames by removing the ones in which the\nvalue of\n$x\nsatisfies\njob($x,@list(\"computer\", \"programmer\")).\n\nThe second query begins by filtering the\nincoming frames to remove those that can satisfy\njob($x, list(\"computer\",@\"programmer\")).\n\nSince the only incoming frame is empty, it checks the data base\nfor\npatterns that satisfy\njob($x, list(\"computer\", \"programmer\")).\n\nSince there generally are entries of this form, the\nclause filters out the empty frame and\nreturns an empty stream of frames.\n\nConsequently, the entire compound query\nreturns an empty stream.", + "token_count": 297, + "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Logic Programming", - "subsection": null, + "subsection": "Is Logic Programming Mathematical Logic?", "chunk_index": 3, - "chunk_id": "Metalinguistic_Abstraction_Logic_Programming_3" + "chunk_id": "Metalinguistic_Abstraction_Is_Logic_Programming_Mathematical_Logic_3" }, { - "content": "In this section, we extend the JavaScript evaluator to support a programming paradigm called nondeterministic computing by building into the evaluator a facility to support\n\n.\n\nNondeterministic computing, like stream processing, is useful for\ngenerate and test applications.\n\nConsider the task of\nstarting with two lists of positive integers and finding a pair of\nintegers one from the first list and one from the second\nlist whose sum is prime.\n\nWe saw how to handle this with finite\nsequence operations in section and\nwith infinite streams in section.\n\nOur approach was to generate the sequence of all possible pairs and filter\nthese to select the pairs whose sum is prime.\n\nWhether we actually generate\nthe entire sequence of pairs first as in chapter , or interleave the\ngenerating and filtering as in chapter , is immaterial to the\nessential image of how the computation is organized.\n\nThe nondeterministic approach evokes a different image.\n\nImagine simply\nthat we choose (in some way) a number from the first list and a number\nfrom the second list and require (using some mechanism) that their\nfunction:\n\n```javascript\nprime_sum_pair_non_det\n prime_sum_pair_non_det_example\n [ 3, [ 20, null ] ]\n\nfunction prime_sum_pair(list1, list2) {\n const a = an_element_of(list1);\n const b = an_element_of(list2);\n require(is_prime(a + b));\n return list(a, b);\n}\n```\n\nIt might seem as if this\nfunction\nmerely restates the problem,\nrather than specifying a way to solve it.\n\nNevertheless, this is a\nlegitimate nondeterministic program.\n\nThe key idea here is that\ncomponents\nin a nondeterministic language\ncan have more than one possible value.\n\nFor instance,\nan_element_of\nmight return any element of the given list.\n\nOur nondeterministic program\nevaluator will work by automatically choosing a possible value and keeping\ntrack of the choice.", - "token_count": 285, + "content": "Consequently, the entire compound query\nreturns an empty stream.\n\nIf a\nclause is processed with a frame in which\nsome of the variables remain unbound (as does\n$x\nin the example above), the system will produce unexpected results.\n\nSimilar\nproblems occur with the use of\njavascript_@predicatethe\nJavaScript predicate cant work if some of its variables are unbound.\n\nSee exercise.\n\nThere is also a much more serious way in which the\nof the query language differs from the\nof mathematical logic.\n\nIn logic, we\ninterpret the statement not $P$ to\nmean that $P$ is not true.\n\nIn the query system,\nhowever, not $P$ means that\n$P$ is not deducible from the knowledge in the\ndata base.\n\nFor example, given the personnel data base of\nsection , the system would\nhappily deduce all sorts of statements,\nsuch as that Ben Bitdiddle is not a baseball fan, that it is not raining\noutside, and that $2 + 2$\nis not 4.\n\nIn other\nwords, the of logic programming languages\nreflects the so-called\nclosed world assumption that all relevant information has been\nincluded in the data base.", + "token_count": 183, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Logic Programming", + "subsection": "Is Logic Programming Mathematical Logic?", + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_Is_Logic_Programming_Mathematical_Logic_4" + }, + { + "content": "In this section, we extend the\nJavaScript\nevaluator to support a\nprogramming paradigm called nondeterministic computing by\nbuilding into the evaluator a facility to support\nautomatic search.\n\nThis is a much more profound change to the language than the\nintroduction of lazy evaluation in\nsection.\n\nNondeterministic computing, like stream processing, is useful for\ngenerate and test applications.\n\nConsider the task of\nstarting with two lists of positive integers and finding a pair of\nintegers one from the first list and one from the second\nlist whose sum is prime.\n\nWe saw how to handle this with finite\nsequence operations in section and\nwith infinite streams in section.\n\nOur approach was to generate the sequence of all possible pairs and filter\nthese to select the pairs whose sum is prime.\n\nWhether we actually generate\nthe entire sequence of pairs first as in chapter , or interleave the\ngenerating and filtering as in chapter , is immaterial to the\nessential image of how the computation is organized.\n\nThe nondeterministic approach evokes a different image.\n\nImagine simply\nthat we choose (in some way) a number from the first list and a number\nfrom the second list and require (using some mechanism) that their\nsum be prime.\n\nThis is expressed by the following\nfunction:\n\n```javascript\nprime_sum_pair_non_det\n prime_sum_pair_non_det_example\n [ 3, [ 20, null ] ]\n\nfunction prime_sum_pair(list1, list2) {\n const a = an_element_of(list1);\n const b = an_element_of(list2);\n require(is_prime(a + b));\n return list(a, b);\n}\n```\n\nIt might seem as if this\nfunction\nmerely restates the problem,\nrather than specifying a way to solve it.\n\nNevertheless, this is a\nlegitimate nondeterministic program.\n\nThe key idea here is that\ncomponents\nin a nondeterministic language\ncan have more than one possible value.\n\nFor instance,\nan_element_of\nmight return any element of the given list.", + "token_count": 295, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", @@ -2380,9 +3620,9 @@ "chunk_id": "Metalinguistic_Abstraction_Nondeterministic_Computing_1" }, { - "content": "Our nondeterministic program\nevaluator will work by automatically choosing a possible value and keeping\ntrack of the choice.\n\nIf a subsequent requirement is not met, the evaluator\nwill try a different choice, and it will keep trying new choices until the\nevaluation succeeds, or until we run out of choices.\n\nJust as the lazy\nevaluator freed the programmer from the details of how values are delayed\nand forced, the nondeterministic program evaluator will free the programmer\nfrom the details of how choices are made.\n\nIt is instructive to contrast the different images of\na component\nrepresents the exploration\nof a set of possible worlds, each determined by a set of choices.\n\nSome of the possible worlds lead to dead ends, while others have\nuseful values.\n\nThe nondeterministic program evaluator supports the\nillusion that time branches, and that our programs have different\npossible execution histories.\n\nWhen we reach a dead end, we can\nrevisit a previous choice point and proceed along a different branch.\n\nThe nondeterministic program evaluator implemented below is called the a new syntactic form called declaration of prime_sum_pair at the declarations of is_prime, an_element_of, and function\n\nas follows:\n\n```javascript\nprime_sum_pair_non_det_example\n is_prime2\n prime_sum_pair_non_det\n\namb-evaluate input:\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\nThe value returned was obtained after the evaluator repeatedly chose elements from each of the lists, until a successful choice was made.\n\nSection introduces\ns automatic search mechanism.\n\nSection presents examples of\nnondeterministic programs, and\nsection gives the details of how\nto implement the\nJavaScript\nevaluator.", - "token_count": 277, - "has_code": true, + "content": "For instance,\nan_element_of\nmight return any element of the given list.\n\nIf a subsequent requirement is not met, the evaluator\nwill try a different choice, and it will keep trying new choices until the\nevaluation succeeds, or until we run out of choices.\n\nJust as the lazy\nevaluator freed the programmer from the details of how values are delayed\nand forced, the nondeterministic program evaluator will free the programmer\nfrom the details of how choices are made.\n\nIt is instructive to contrast the different images of\ntime evoked by\nnondeterministic evaluation and stream processing.\n\nStream processing\nuses lazy evaluation to decouple the time when the stream of possible\nanswers is assembled from the time when the actual stream elements are\nproduced.\n\nThe evaluator supports the illusion that all the possible\nanswers are laid out before us in a timeless sequence.\n\nWith\nnondeterministic evaluation,\na component\nrepresents the exploration\nof a set of possible worlds, each determined by a set of choices.\n\nSome of the possible worlds lead to dead ends, while others have\nuseful values.\n\nThe nondeterministic program evaluator supports the\nillusion that time branches, and that our programs have different\npossible execution histories.\n\nWhen we reach a dead end, we can\nrevisit a previous choice point and proceed along a different branch.\n\nThe nondeterministic program evaluator implemented below is called the\nevaluator because it is based on\na new syntactic form\ncalled.\n\nWe can type the above\ndeclaration\nof\nprime_sum_pair\nat the evaluator driver loop (along with\ndeclarations\nof\nis_prime,\nan_element_of,\nand ) and run the\nfunction\nas follows:", + "token_count": 260, + "has_code": false, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", "subsection": null, @@ -2390,7 +3630,17 @@ "chunk_id": "Metalinguistic_Abstraction_Nondeterministic_Computing_2" }, { - "content": "Section describes the implementation of the\n\nThe following puzzle (adapted from The software company\n\nWe can determine who moves into which office in a straightforward way by enumerating all the possibilities and imposing the given restrictions:\n\n```javascript\noffice_move\n distinct\n office_move_example\n [ 'alyssa', [ 3, null ] ]\n\nfunction office_move() {\n const alyssa = amb(1, 2, 3, 4, 5);\n const ben = amb(1, 2, 3, 4, 5);\n const cy = amb(1, 2, 3, 4, 5);\n const lem = amb(1, 2, 3, 4, 5);\n const louis = amb(1, 2, 3, 4, 5);\n require(distinct(list(alyssa, ben, cy, lem, louis)));\n require(alyssa !== 5);\n require(ben !== 1);\n require(cy !== 5);\n require(cy !== 1);\n require(lem > ben);\n require(math_abs(louis - cy) !== 1);\n require(math_abs(cy - ben) !== 1);\n return list(list(\"alyssa\", alyssa),\n list(\"ben\", ben),\n list(\"cy\", cy),\n list(\"lem\", lem),\n list(\"louis\", louis));\n}\n```\n\n```javascript\noffice_move_example\n\noffice_move();\n\nhead(office_move());\n```\n\nEvaluating the expression office_move() produces the result\n\n```javascript\nlist(list(\"alyssa\", 3), list(\"ben\", 2), list(\"cy\", 4),\n list(\"lem\", 5), list(\"louis\", 1))\n```\n\nAlthough this simple\nfunction\nworks, it is very slow.\n\nExercises\nand discuss some possible\nimprovements.\n\nPrograms designed to accept natural language as input usually start by\nattempting to parse the input, that is, to match the input\nagainst some grammatical structure.\n\nFor example, we might try to\nrecognize simple sentences consisting of an article followed by a noun\nfollowed by a verb, such as The cat eats.\n\nTo accomplish\nsuch an analysis, we must be able to identify the parts of speech of\nindividual words.\n\nWe could start with some lists that classify various\nwords:\n\n```javascript\nnouns\n\nconst nouns = list(\"noun\", \"student\", \"professor\", \"cat\", \"class\");\n\nconst verbs = list(\"verb\", \"studies\", \"lectures\", \"eats\", \"sleeps\");\n\nconst articles = list(\"article\", \"the\", \"a\");\n```\n\nWe also need a\ngrammar , that is, a set of rules describing how\ngrammatical elements are composed from simpler elements.", + "content": "We can type the above\ndeclaration\nof\nprime_sum_pair\nat the evaluator driver loop (along with\ndeclarations\nof\nis_prime,\nan_element_of,\nand ) and run the\nfunction\nas follows:\n\nThe value returned was obtained after the evaluator repeatedly chose elements from each of the lists, until a successful choice was made.\n\nSection introduces\nand explains how it supports nondeterminism\nthrough the evaluator s automatic search mechanism.\n\nSection presents examples of\nnondeterministic programs, and\nsection gives the details of how\nto implement the evaluator by modifying the\nordinary\nJavaScript\nevaluator.", + "token_count": 87, + "has_code": false, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": null, + "chunk_index": 3, + "chunk_id": "Metalinguistic_Abstraction_Nondeterministic_Computing_3" + }, + { + "content": "Section describes the\nimplementation of the evaluator.\n\nFirst,\nhowever, we give some examples of how it can be used.\n\nThe advantage of\nnondeterministic programming is that we can suppress the details of how\nsearch is carried out, thereby\nexpressing our programs at a higher level of\nabstraction.\n\nThe following puzzle (adapted from\n)\nis typical of a large class of simple logic puzzles:\nThe software company\nGargle is expanding, and Alyssa, Ben, Cy, Lem, and Louis\nare moving into a row of five private offices in a\nnew building.\n\nAlyssa does not move into the last office.\n\nBen does not\nmove into the first office.\n\nCy takes neither the first nor the last office.\n\nLem moves into an office after Ben's.\n\nLouis's office is not next to\nCy's.\n\nCy's office is not next to Ben's.\n\nWho moves into which office?\n\nWe can determine who moves into which office in a straightforward way by enumerating all the possibilities and imposing the given restrictions:\n\n```javascript\noffice_move\n distinct\n office_move_example\n [ 'alyssa', [ 3, null ] ]\n\nfunction office_move() {\n const alyssa = amb(1, 2, 3, 4, 5);\n const ben = amb(1, 2, 3, 4, 5);\n const cy = amb(1, 2, 3, 4, 5);\n const lem = amb(1, 2, 3, 4, 5);\n const louis = amb(1, 2, 3, 4, 5);\n require(distinct(list(alyssa, ben, cy, lem, louis)));\n require(alyssa !== 5);\n require(ben !== 1);\n require(cy !== 5);\n require(cy !== 1);\n require(lem > ben);\n require(math_abs(louis - cy) !== 1);\n require(math_abs(cy - ben) !== 1);\n return list(list(\"alyssa\", alyssa),\n list(\"ben\", ben),\n list(\"cy\", cy),\n list(\"lem\", lem),\n list(\"louis\", louis));\n}\n```\n\n```javascript\noffice_move_example\n\noffice_move();\n\nhead(office_move());\n```\n\nEvaluating the expression office_move() produces the result\n\n```javascript\nlist(list(\"alyssa\", 3), list(\"ben\", 2), list(\"cy\", 4),\n list(\"lem\", 5), list(\"louis\", 1))\n```\n\nAlthough this simple\nfunction\nworks, it is very slow.\n\nExercises\nand discuss some possible\nimprovements.", "token_count": 298, "has_code": true, "chapter": "Metalinguistic Abstraction", @@ -2400,8 +3650,8 @@ "chunk_id": "Metalinguistic_Abstraction_Examples_of_Nondeterministic_Programs_1" }, { - "content": "We also need a\ngrammar , that is, a set of rules describing how\ngrammatical elements are composed from simpler elements.\n\nA very\nsimple grammar might stipulate that a sentence always consists of two\npieces a noun phrase followed by a verb and that a noun\nphrase consists of an article followed by a noun.\n\nWith this grammar, the\nsentence The cat eats is parsed as follows:\n\n```javascript\nlist(\"sentence\",\n list(\"noun-phrase\", list(\"article\", \"the\"), list(\"noun\", \"cat\"),\n list(\"verb\", \"eats\"))\n```\n\nWe can generate such a parse with a simple program that has separate\nfunctions\nfor each of the grammatical rules.\n\nTo parse a sentence, we identify its\ntwo constituent pieces and return a list of these two elements, tagged with\nthe symbol\n\n```javascript\nparse_sentence\n parse_noun_phrase\n nouns\n parse_input_example\n [ \"sentence\", [ [\"noun-phrase\", [[\"article\", [\"the\", null]], [[\"noun\", [\"cat\", null]], null]]], [[\"verb\", [\"eats\", null]], null]]]\n\nfunction parse_sentence() {\n return list(\"sentence\",\n parse_noun_phrase(),\n parse_word(verbs));\n}\n```\n\nA noun phrase, similarly, is parsed by finding an article followed by a noun:\n\n```javascript\nparse_noun_phrase\n nouns\n parse_word\n parse_input_example\n\nfunction parse_noun_phrase() {\n return list(\"noun-phrase\",\n parse_word(articles),\n parse_word(nouns));\n}\n```\n\nAt the lowest level, parsing boils down to repeatedly checking that\nthe next\nnot-yet-parsed\nword is a member of the list of words for the\nrequired part of speech.\n\nTo implement this, we maintain a global\nvariable\nnot_yet_parsed,\nwhich is the input that has not yet been parsed.\n\nEach time we check a word,\nwe require that\nnot_yet_parsed\nmust be nonempty and that it should begin with a word from the designated\nlist.\n\nIf so, we remove that word from\nnot_yet_parsed\nand return the word together with its part of speech (which is found at\nthe head of the list):\n\n```javascript\nparse_word\n unparsed\n parse_input_example\n\nfunction parse_word(word_list) {\n require(! is_null(not_yet_parsed));\n require(! is_null(member(head(not_yet_parsed), tail(word_list))));\n const found_word = head(not_yet_parsed);\n not_yet_parsed = tail(not_yet_parsed);\n return list(head(word_list), found_word);\n}\n```", - "token_count": 300, + "content": "Exercises\nand discuss some possible\nimprovements.\n\nFor example, we might try to\nrecognize simple sentences consisting of an article followed by a noun\nfollowed by a verb, such as The cat eats.\n\nTo accomplish\nsuch an analysis, we must be able to identify the parts of speech of\nindividual words.\n\nWe could start with some lists that classify various\nwords:\n\n```javascript\nnouns\n\nconst nouns = list(\"noun\", \"student\", \"professor\", \"cat\", \"class\");\n\nconst verbs = list(\"verb\", \"studies\", \"lectures\", \"eats\", \"sleeps\");\n\nconst articles = list(\"article\", \"the\", \"a\");\n```\n\nWe also need a\ngrammar , that is, a set of rules describing how\ngrammatical elements are composed from simpler elements.\n\nA very\nsimple grammar might stipulate that a sentence always consists of two\npieces a noun phrase followed by a verb and that a noun\nphrase consists of an article followed by a noun.\n\nWith this grammar, the\nsentence The cat eats is parsed as follows:\n\n```javascript\nlist(\"sentence\",\n list(\"noun-phrase\", list(\"article\", \"the\"), list(\"noun\", \"cat\"),\n list(\"verb\", \"eats\"))\n```\n\nWe can generate such a parse with a simple program that has separate\nfunctions\nfor each of the grammatical rules.\n\nTo parse a sentence, we identify its\ntwo constituent pieces and return a list of these two elements, tagged with\nthe symbol :\n\n```javascript\nparse_sentence\n parse_noun_phrase\n nouns\n parse_input_example\n [ \"sentence\", [ [\"noun-phrase\", [[\"article\", [\"the\", null]], [[\"noun\", [\"cat\", null]], null]]], [[\"verb\", [\"eats\", null]], null]]]\n\nfunction parse_sentence() {\n return list(\"sentence\",\n parse_noun_phrase(),\n parse_word(verbs));\n}\n```\n\nA noun phrase, similarly, is parsed by finding an article followed by a noun:\n\n```javascript\nparse_noun_phrase\n nouns\n parse_word\n parse_input_example\n\nfunction parse_noun_phrase() {\n return list(\"noun-phrase\",\n parse_word(articles),\n parse_word(nouns));\n}\n```\n\nAt the lowest level, parsing boils down to repeatedly checking that\nthe next\nnot-yet-parsed\nword is a member of the list of words for the\nrequired part of speech.", + "token_count": 291, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", @@ -2410,8 +3660,8 @@ "chunk_id": "Metalinguistic_Abstraction_Examples_of_Nondeterministic_Programs_2" }, { - "content": "If so, we remove that word from\nnot_yet_parsed\nand return the word together with its part of speech (which is found at\nthe head of the list):\n\nTo start the parsing, all we need to do is set not_yet_parsed to be the entire input, try to parse a sentence, and check that\n\nnothing is left over:\n\n```javascript\nunparsed\n\nlet not_yet_parsed = null;\n```\n\n```javascript\nparse_input\n unparsed\n parse_sentence\n parse_input_example\n\nfunction parse_input(input) {\n not_yet_parsed = input;\n const sent = parse_sentence();\n require(is_null(not_yet_parsed));\n return sent;\n}\n```\n\nWe can now try the parser and verify that it works for our simple test sentence:\n\n```javascript\nparse_input_example\n parse_input\n\namb-evaluate input:\n\nparse_input(list(\"the\", \"cat\", \"eats\"));\n\nparse_input(list(\"the\", \"cat\", \"eats\"));\n```\n\nThe\n\nLet s add to our grammar a list of prepositions:\n\n```javascript\nprepositions\n\nconst prepositions = list(\"prep\", \"for\", \"to\", \"in\", \"by\", \"with\");\n```\n\nand define a prepositional phrase (e.g., for the cat ) to be a preposition followed by a noun phrase:\n\n```javascript\nparse_prepositional_phrase\n parse_word\n prepositions\n parse_input_example_2\n [ \"sentence\", [ [ \"noun-phrase\", [ [ \"simple-noun-phrase\", [[\"article\", [\"the\", null]], [[\"noun\", [\"student\", null]], null]]], [ [ \"prep-phrase\", [ [\"prep\", [\"with\", null]], [ [ \"simple-noun-phrase\", [[\"article\", [\"the\", null]], [[\"noun\", [\"cat\", null]], null]]], null]]], null]]], [ [ \"verb-phrase\", [ [\"verb\", [\"sleeps\", null]], [ [ \"prep-phrase\", [ [\"prep\", [\"in\", null]], [ [ \"simple-noun-phrase\", [[\"article\", [\"the\", null]], [[\"noun\", [\"class\", null]], null]]], null]]], null]]], null]]]\n\nfunction parse_prepositional_phrase() {\n return list(\"prep-phrase\",\n parse_word(prepositions),\n parse_noun_phrase());\n}\n```\n\nNow we can define a sentence to be a noun phrase followed by a verb phrase, where a verb phrase can be either a verb\n\nor a verb phrase extended by a prepositional phrase:\n\n```javascript\nparse_sentence_2\n parse_prepositional_phrase\n parse_word\n parse_noun_phrase_2\n parse_input_example_2\n\nfunction parse_sentence() {\n return list(\"sentence\",\n parse_noun_phrase(),\n parse_verb_phrase());\n}\nfunction parse_verb_phrase() {\n function maybe_extend(verb_phrase) {\n return amb(verb_phrase,\n maybe_extend(list(\"verb-phrase\",\n verb_phrase,\n parse_prepositional_phrase())));\n }\n return maybe_extend(parse_word(verbs));\n}\n```\n\n```javascript\nparse_input_2\n unparsed\n parse_sentence_2\n\nfunction parse_input(input) {\n not_yet_parsed = input;\n const sent = parse_sentence();\n require(is_null(not_yet_parsed));\n return sent;\n}\n```", - "token_count": 311, + "content": "At the lowest level, parsing boils down to repeatedly checking that\nthe next\nnot-yet-parsed\nword is a member of the list of words for the\nrequired part of speech.\n\nEach time we check a word,\nwe require that\nnot_yet_parsed\nmust be nonempty and that it should begin with a word from the designated\nlist.\n\nIf so, we remove that word from\nnot_yet_parsed\nand return the word together with its part of speech (which is found at\nthe head of the list):\n\n```javascript\nparse_word\n unparsed\n parse_input_example\n\nfunction parse_word(word_list) {\n require(! is_null(not_yet_parsed));\n require(! is_null(member(head(not_yet_parsed), tail(word_list))));\n const found_word = head(not_yet_parsed);\n not_yet_parsed = tail(not_yet_parsed);\n return list(head(word_list), found_word);\n}\n```\n\nTo start the parsing, all we need to do is set not_yet_parsed to be the entire input, try to parse a sentence, and check that\n\nnothing is left over:\n\n```javascript\nunparsed\n\nlet not_yet_parsed = null;\n```\n\n```javascript\nparse_input\n unparsed\n parse_sentence\n parse_input_example\n\nfunction parse_input(input) {\n not_yet_parsed = input;\n const sent = parse_sentence();\n require(is_null(not_yet_parsed));\n return sent;\n}\n```\n\nWe can now try the parser and verify that it works for our simple test sentence:\n\n```javascript\nparse_input_example\n parse_input\n\namb-evaluate input:\n\nparse_input(list(\"the\", \"cat\", \"eats\"));\n\nparse_input(list(\"the\", \"cat\", \"eats\"));\n\nStarting a new problem\namb-evaluate value:\nlist(\"sentence\",\n list(\"noun-phrase\", list(\"article\", \"the\"), list(\"noun\", \"cat\")),\n list(\"verb\", \"eats\"))\n```\n\nThe evaluator is useful here because it is\nconvenient to express the parsing constraints with the aid of.\n\nAutomatic search and backtracking\nreally pay off, however, when we consider more complex grammars where there\nare choices for how the units can be decomposed.\n\nLet s add to our grammar a list of prepositions:\n\n```javascript\nprepositions\n\nconst prepositions = list(\"prep\", \"for\", \"to\", \"in\", \"by\", \"with\");\n```\n\nand define a prepositional phrase (e.g., for the cat ) to be a preposition followed by a noun phrase:", + "token_count": 287, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", @@ -2420,8 +3670,8 @@ "chunk_id": "Metalinguistic_Abstraction_Examples_of_Nondeterministic_Programs_3" }, { - "content": "or a verb phrase extended by a prepositional phrase:\n\nWhile we re at it, we can also elaborate the definition of noun\nphrases to permit such things as a cat in the class.\n\nWhat\nwe used to call a noun phrase, we ll now call a simple noun phrase,\nand a noun phrase will now be either a simple noun phrase or a noun phrase\nextended by a prepositional phrase:\n\n```javascript\nparse_noun_phrase_2\n parse_prepositional_phrase\n parse_word\n nouns\n parse_input_example_2\n\nfunction parse_simple_noun_phrase() {\n return list(\"simple-noun-phrase\",\n parse_word(articles),\n parse_word(nouns));\n}\nfunction parse_noun_phrase() {\n function maybe_extend(noun_phrase) {\n return amb(noun_phrase,\n maybe_extend(list(\"noun-phrase\",\n noun_phrase,\n parse_prepositional_phrase())));\n }\n return maybe_extend(parse_simple_noun_phrase());\n}\n```\n\nOur new grammar lets us parse more complex sentences.\n\nFor example\n\n```javascript\nparse_input_example_2\n parse_input_2\n\nparse_input(list(\"the\", \"student\", \"with\", \"the\", \"cat\",\n \"sleeps\", \"in\", \"the\", \"class\"));\n```\n\nproduces\n\n```javascript\nlist(\"sentence\",\n list(\"noun-phrase\",\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"), list(\"noun\", \"student\")),\n list(\"prep-phrase\", list(\"prep\", \"with\"),\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n list(\"noun\", \"cat\")))),\n list(\"verb-phrase\",\n list(\"verb\", \"sleeps\"),\n list(\"prep-phrase\", list(\"prep\", \"in\"),\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n list(\"noun\", \"class\")))))\n```\n\nObserve that a given input may have more than one legal parse.\n\nIn the\nsentence The professor lectures to the student with the cat,\nit may be that the professor is lecturing with the cat, or that the student\nhas the cat.\n\nOur nondeterministic program finds both possibilities:\n\n```javascript\nmultiple_legal_parses\n parse_input_2\n\nparse_input(list(\"the\", \"professor\", \"lectures\",\n \"to\", \"the\", \"student\", \"with\", \"the\", \"cat\"));\n```\n\nproduces\n\n```javascript\nlist(\"sentence\",\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"), list(\"noun\", \"professor\")),\n list(\"verb-phrase\",\n list(\"verb-phrase\",\n list(\"verb\", \"lectures\"),\n list(\"prep-phrase\", list(\"prep\", \"to\"),\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n\t\t list(\"noun\", \"student\")))),\n list(\"prep-phrase\", list(\"prep\", \"with\"),\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n list(\"noun\", \"cat\")))))\n```\n\nAsking the evaluator to retry yields\n\n```javascript\nlist(\"sentence\",\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"), list(\"noun\", \"professor\")),\n list(\"verb-phrase\",\n list(\"verb\", \"lectures\"),\n list(\"prep-phrase\", list(\"prep\", \"to\"),\n list(\"noun-phrase\",\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n list(\"noun\", \"student\")),\n list(\"prep-phrase\", list(\"prep\", \"with\"),\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n list(\"noun\", \"cat\")))))))\n```", - "token_count": 277, + "content": "and define a prepositional phrase (e.g., for the cat ) to be a preposition followed by a noun phrase:\n\nNow we can define a sentence to be a noun phrase followed by a verb phrase, where a verb phrase can be either a verb\n\nor a verb phrase extended by a prepositional phrase:\n\n```javascript\nparse_sentence_2\n parse_prepositional_phrase\n parse_word\n parse_noun_phrase_2\n parse_input_example_2\n\nfunction parse_sentence() {\n return list(\"sentence\",\n parse_noun_phrase(),\n parse_verb_phrase());\n}\nfunction parse_verb_phrase() {\n function maybe_extend(verb_phrase) {\n return amb(verb_phrase,\n maybe_extend(list(\"verb-phrase\",\n verb_phrase,\n parse_prepositional_phrase())));\n }\n return maybe_extend(parse_word(verbs));\n}\n```\n\n```javascript\nparse_input_2\n unparsed\n parse_sentence_2\n\nfunction parse_input(input) {\n not_yet_parsed = input;\n const sent = parse_sentence();\n require(is_null(not_yet_parsed));\n return sent;\n}\n```\n\nWhile we re at it, we can also elaborate the definition of noun\nphrases to permit such things as a cat in the class.\n\nWhat\nwe used to call a noun phrase, we ll now call a simple noun phrase,\nand a noun phrase will now be either a simple noun phrase or a noun phrase\nextended by a prepositional phrase:\n\n```javascript\nparse_noun_phrase_2\n parse_prepositional_phrase\n parse_word\n nouns\n parse_input_example_2\n\nfunction parse_simple_noun_phrase() {\n return list(\"simple-noun-phrase\",\n parse_word(articles),\n parse_word(nouns));\n}\nfunction parse_noun_phrase() {\n function maybe_extend(noun_phrase) {\n return amb(noun_phrase,\n maybe_extend(list(\"noun-phrase\",\n noun_phrase,\n parse_prepositional_phrase())));\n }\n return maybe_extend(parse_simple_noun_phrase());\n}\n```\n\nOur new grammar lets us parse more complex sentences.\n\nFor example\n\n```javascript\nparse_input_example_2\n parse_input_2\n\nparse_input(list(\"the\", \"student\", \"with\", \"the\", \"cat\",\n \"sleeps\", \"in\", \"the\", \"class\"));\n```\n\nproduces\n\n```javascript\nlist(\"sentence\",\n list(\"noun-phrase\",\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"), list(\"noun\", \"student\")),\n list(\"prep-phrase\", list(\"prep\", \"with\"),\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n list(\"noun\", \"cat\")))),\n list(\"verb-phrase\",\n list(\"verb\", \"sleeps\"),\n list(\"prep-phrase\", list(\"prep\", \"in\"),\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n list(\"noun\", \"class\")))))\n```\n\nObserve that a given input may have more than one legal parse.\n\nIn the\nsentence The professor lectures to the student with the cat,\nit may be that the professor is lecturing with the cat, or that the student\nhas the cat.\n\nOur nondeterministic program finds both possibilities:\n\n```javascript\nmultiple_legal_parses\n parse_input_2\n\nparse_input(list(\"the\", \"professor\", \"lectures\",\n \"to\", \"the\", \"student\", \"with\", \"the\", \"cat\"));\n```\n\nproduces\n\n```javascript\nlist(\"sentence\",\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"), list(\"noun\", \"professor\")),\n list(\"verb-phrase\",\n list(\"verb-phrase\",\n list(\"verb\", \"lectures\"),\n list(\"prep-phrase\", list(\"prep\", \"to\"),\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n\t\t list(\"noun\", \"student\")))),\n list(\"prep-phrase\", list(\"prep\", \"with\"),\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n list(\"noun\", \"cat\")))))\n```", + "token_count": 336, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", @@ -2430,8 +3680,18 @@ "chunk_id": "Metalinguistic_Abstraction_Examples_of_Nondeterministic_Programs_4" }, { - "content": "The evaluation of an ordinary\nJavaScript program\nmay return a value, may never terminate, or may signal an error.\n\nIn nondeterministic\nJavaScript\nthe evaluation of\na program\nmay in addition result in the discovery of\na dead end, in which case evaluation must backtrack to a previous choice\npoint.\n\nThe interpretation of nondeterministic\nJavaScript\nis complicated by this extra case.\n\nWe will construct the\nJavaScript\nby modifying the.\na component\nis accomplished by calling an\nfunction\nproduced by analysis of that\ncomponent.\n\nThe difference between the interpretation of ordinary\nJavaScript\nand the interpretation of nondeterministic\nJavaScript\nwill be entirely\nin the execution\nfunctions.\n\nRecall that the\nfunctions\nfor the ordinary evaluator take one argument: the environment of execution.\n\nIn contrast, the execution\nfunctions\nin the\nfunctions\ncalled\ncontinuation functions.\n\nThe evaluation of\na component\nwill finish by calling one of these two\ncontinuations: If the evaluation results in a value, the\nsuccess continuation is called with that value; if the evaluation\nresults in the discovery of a dead end, the\nfailure continuation is called.\n\nConstructing and calling\nappropriate continuations is the mechanism by which the nondeterministic\nevaluator implements backtracking.\n\nIt is the job of the success continuation to receive a value and proceed\nwith the computation.\n\nAlong with that value, the success continuation is\npassed another failure continuation, which is to be called subsequently if\nthe use of that value leads to a dead end.\n\nIt is the job of the failure continuation to try another branch of the\nnondeterministic process.\n\nThe essence of the nondeterministic\nlanguage is in the fact that\ncomponents\nmay represent choices among\nalternatives.\n\nThe evaluation of such\na component\nmust proceed with\none of the indicated alternative choices, even though it is not known\nin advance which choices will lead to acceptable results.", - "token_count": 298, + "content": "produces\n\n```javascript\nlist(\"sentence\",\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"), list(\"noun\", \"professor\")),\n list(\"verb-phrase\",\n list(\"verb\", \"lectures\"),\n list(\"prep-phrase\", list(\"prep\", \"to\"),\n list(\"noun-phrase\",\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n list(\"noun\", \"student\")),\n list(\"prep-phrase\", list(\"prep\", \"with\"),\n list(\"simple-noun-phrase\",\n list(\"article\", \"the\"),\n list(\"noun\", \"cat\")))))))\n```", + "token_count": 29, + "has_code": true, + "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Examples of Nondeterministic Programs", + "chunk_index": 5, + "chunk_id": "Metalinguistic_Abstraction_Examples_of_Nondeterministic_Programs_5" + }, + { + "content": "The evaluation of an ordinary\nJavaScript program\nmay return a value, may never terminate, or may signal an error.\n\nIn nondeterministic\nJavaScript\nthe evaluation of\na program\nmay in addition result in the discovery of\na dead end, in which case evaluation must backtrack to a previous choice\npoint.\n\nThe interpretation of nondeterministic\nJavaScript\nis complicated by this extra case.\n\nWe will construct the evaluator for\nnondeterministic\nJavaScript\nby modifying the\nanalyzing evaluator of\nsection.\n\nAs in the analyzing evaluator, evaluation of\na component\nis accomplished by calling an\nexecution\nfunction\nproduced by analysis of that\ncomponent.\n\nThe difference between the interpretation of ordinary\nJavaScript\nand the interpretation of nondeterministic\nJavaScript\nwill be entirely\nin the execution\nfunctions.\n\nRecall that the\nexecution\nfunctions\nfor the ordinary evaluator take one argument: the environment of execution.\n\nIn contrast, the execution\nfunctions\nin the evaluator take three arguments:\nthe environment, and two\nfunctions\ncalled\ncontinuation functions.\n\nThe evaluation of\na component\nwill finish by calling one of these two\ncontinuations: If the evaluation results in a value, the\nsuccess continuation is called with that value; if the evaluation\nresults in the discovery of a dead end, the\nfailure continuation is called.\n\nConstructing and calling\nappropriate continuations is the mechanism by which the nondeterministic\nevaluator implements backtracking.\n\nIt is the job of the success continuation to receive a value and proceed\nwith the computation.\n\nAlong with that value, the success continuation is\npassed another failure continuation, which is to be called subsequently if\nthe use of that value leads to a dead end.\n\nIt is the job of the failure continuation to try another branch of the\nnondeterministic process.\n\nThe essence of the nondeterministic\nlanguage is in the fact that\ncomponents\nmay represent choices among\nalternatives.", + "token_count": 292, "has_code": false, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", @@ -2440,8 +3700,8 @@ "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_1" }, { - "content": "The evaluation of such\na component\nmust proceed with\none of the indicated alternative choices, even though it is not known\nin advance which choices will lead to acceptable results.\n\nTo deal\nwith this, the evaluator picks one of the alternatives and passes this\nvalue to the success continuation.\n\nTogether with this value, the\nevaluator constructs and passes along a failure continuation that can\nbe called later to choose a different alternative.\n\nA failure is triggered during evaluation (that is, a failure\ncontinuation is called) when a user program explicitly rejects the\ncurrent line of attack (for example, a call to\namb(),\nan expression that always\nfails see section ).\n\nThe failure\ncontinuation in hand at that point will cause the most recent choice point\nto choose another alternative.\n\nIf there are no more alternatives to be\nconsidered at that choice point, a failure at an earlier choice point\nis triggered, and so on.\n\nFailure continuations are also invoked by\nthe driver loop in response to a\nretry\nrequest, to find another value of the\nprogram.\n\nIn addition, if a side-effect operation (such as assignment to a\nvariable) occurs on a branch of the process resulting from a choice,\nit may be necessary, when the process finds a dead end, to undo the\nside effect before making a new choice.\n\nThis is accomplished by\nhaving the side-effect operation produce a failure continuation that\nundoes the side effect and propagates the failure.\n\nIn summary, failure continuations are constructed by - - to provide a mechanism to make alternative choices if the current choice made by the -\n\n- the top-level driver to provide a mechanism to report failure when the choices are exhausted; - - assignments to intercept failures and undo assignments\n\nduring backtracking.", - "token_count": 293, + "content": "The essence of the nondeterministic\nlanguage is in the fact that\ncomponents\nmay represent choices among\nalternatives.\n\nTo deal\nwith this, the evaluator picks one of the alternatives and passes this\nvalue to the success continuation.\n\nTogether with this value, the\nevaluator constructs and passes along a failure continuation that can\nbe called later to choose a different alternative.\n\nA failure is triggered during evaluation (that is, a failure\ncontinuation is called) when a user program explicitly rejects the\ncurrent line of attack (for example, a call to\nmay result in execution of\namb(),\nan expression that always\nfails see section ).\n\nThe failure\ncontinuation in hand at that point will cause the most recent choice point\nto choose another alternative.\n\nIf there are no more alternatives to be\nconsidered at that choice point, a failure at an earlier choice point\nis triggered, and so on.\n\nFailure continuations are also invoked by\nthe driver loop in response to a\nretry\nrequest, to find another value of the\nprogram.\n\nIn addition, if a side-effect operation (such as assignment to a\nvariable) occurs on a branch of the process resulting from a choice,\nit may be necessary, when the process finds a dead end, to undo the\nside effect before making a new choice.\n\nThis is accomplished by\nhaving the side-effect operation produce a failure continuation that\nundoes the side effect and propagates the failure.\n\nIn summary, failure continuations are constructed by - - expressions to provide a mechanism to make alternative choices if the current choice made by the\n\nexpression leads to a dead end; - - the top-level driver to provide a mechanism to report failure when the choices are exhausted; - -\n\nassignments to intercept failures and undo assignments during backtracking.", + "token_count": 292, "has_code": false, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", @@ -2450,8 +3710,8 @@ "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_2" }, { - "content": "during backtracking.\n\nFailures are initiated only when a dead end is encountered.\n\nThis occurs\n-\n-\nif the user program executes\n\n```javascript\namb();\n```\n\n- - if the user types retry at the top-level driver.\n\nFailure continuations are also called during processing of a failure: - - When the failure continuation created by an assignment finishes undoing a side effect,\n\nit calls the failure continuation it intercepted, in order to propagate the failure back to the choice point that led to this assignment or to\n\nthe top level. - - When the failure continuation for an\n\nThe syntax- and data-representation functions for the function, are identical to those in the evaluator of section , except for the fact that we need\n\nadditional syntax functions to recognize the amb syntactic form:\n\n```javascript\nis_amb_amb\n functions_4_1_2\n\nfunction is_amb(component) {\n return is_tagged_list(component, \"application\") &&\n is_name(function_expression(component)) &&\n symbol_of_name(function_expression(component)) === \"amb\";\n}\nfunction amb_choices(component) {\n return arg_expressions(component);\n}\n```\n\n```javascript\n\\newpage\\noindent\n\tWe continue to use the parse function of\n\tsection, which\n\tdoesn't support amb as a syntactic\n\tform and instead treats amb($\\ldots$) as\n\ta function application. The function\n\tis_amb ensures that\n\twhenever the name\n\tamb appears as the function\n\texpression of an application, the evaluator treats the\n\tapplication as\n\ta nondeterministic choice point.\n```\n\nWe must also add to the dispatch in\n\n```javascript\nsuch expressions and generate an appropriate execution\n function:\n```\n\n```javascript\nis_amb_case_amb\n all_solutions_test_5\n\n$\\ldots$\n: is_amb(component)\n? analyze_amb(component)\n: is_application(component)\n$\\ldots$\n```\n\nThe top-level function evaluate given in section ) analyzes the given component and applies the resulting execution function to the given environment, together with two\n\ngiven continuations:\n\n```javascript\nanalyze_amb_headline\n\n// functions from SICP JS 4.3.3\n```", - "token_count": 274, + "content": "assignments to intercept failures and undo assignments during backtracking.\n\nThis occurs\n-\n-\nif the user program executes\namb();\n-\n-\nif the user types\nretry\nat the top-level driver.\n\nFailure continuations are also called during processing of a failure: - - When the failure continuation created by an assignment finishes undoing a side effect,\n\nit calls the failure continuation it intercepted, in order to propagate the failure back to the choice point that led to this assignment or to\n\nthe top level. - - When the failure continuation for an runs out of choices, it calls the failure continuation that was originally given to\n\nthe , in order to propagate the failure back to the previous choice point or to the top level.\n\nThe syntax- and data-representation functions for the evaluator, and also the basic function, are identical to those in the evaluator of section , except for\n\nthe fact that we need additional syntax functions to recognize the amb syntactic form:\n\n```javascript\nis_amb_amb\n functions_4_1_2\n\nfunction is_amb(component) {\n return is_tagged_list(component, \"application\") &&\n is_name(function_expression(component)) &&\n symbol_of_name(function_expression(component)) === \"amb\";\n}\nfunction amb_choices(component) {\n return arg_expressions(component);\n}\n```\n\n\\newpage\\noindent We continue to use the parse function of section, which doesn't support amb as a syntactic form and instead treats amb($\\ldots$) as a function application.\n\nThe function is_amb ensures that whenever the name amb appears as the function expression of an application, the evaluator treats the application as a nondeterministic choice point.\n\nWe must also add to the dispatch in a clause that will recognize such expressions and generate an appropriate execution function:\n\n```javascript\nis_amb_case_amb\n all_solutions_test_5\n\n$\\ldots$\n: is_amb(component)\n? analyze_amb(component)\n: is_application(component)\n$\\ldots$\n```\n\nThe top-level function (similar to the version of evaluate given in section ) analyzes the given component and applies the resulting execution function to the\n\ngiven environment, together with two given continuations:", + "token_count": 304, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", @@ -2460,8 +3720,8 @@ "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_3" }, { - "content": "given continuations:\n\n```javascript\nanalyze_amb\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n analyze_amb_headline\n is_amb_amb\n analyze_literal_amb\n analyze_variable_amb\n analyze_lambda_amb\n analyze_sequence_amb\n analyze_declaration_amb\n analyze_assignment_amb\n analyze_if_amb\n scan_out_declarations\n analyze_block_amb\n analyze_return_statement_amb\n analyze_application_amb\n analyze_amb_amb\n\nfunction analyze(component) {\n return is_literal(component)\n ? analyze_literal(component)\n : is_name(component)\n ? analyze_name(component)\n : is_amb(component)\n ? analyze_amb(component)\n : is_application(component)\n ? analyze_application(component)\n : is_operator_combination(component)\n ? analyze(operator_combination_to_application(component))\n : is_conditional(component)\n ? analyze_conditional(component)\n : is_lambda_expression(component)\n ? analyze_lambda_expression(component)\n : is_sequence(component)\n ? analyze_sequence(sequence_statements(component))\n : is_block(component)\n ? analyze_block(component)\n : is_return_statement(component)\n ? analyze_return_statement(component)\n : is_function_declaration(component)\n ? analyze(function_decl_to_constant_decl(component))\n : is_declaration(component)\n ? analyze_declaration(component)\n : is_assignment(component)\n ? analyze_assignment(component)\n : error(component, \"unknown syntax -- analyze\");\n}\n```\n\n```javascript\nambeval\n analyze_amb\n all_solutions_test_4\n\nfunction ambeval(component, env, succeed, fail) {\n return analyze(component)(env, succeed, fail);\n}\n```\n\nA success\nfunction\nof two arguments: the value just obtained and another failure continuation to\nbe used if that value leads to a subsequent failure.\n\nA\nfunction\nof no arguments.\n\nSo\nthe general form of an\nfunction\nis\n\n```javascript\n(env, succeed, fail) => {\n // $\\texttt{succeed}\\,$ is $\\texttt{(value, fail) =>}~\\ldots$\n // $\\texttt{fail}\\,$ is $\\texttt{() =>}~\\ldots$\n $\\ldots$\n}\n```\n\nFor example, executing\n\n```javascript\nambeval(component,\n the_global_environment,\n (value, fail) => value,\n () => \"failed\");\n```\n\nwill attempt to evaluate the given\ncomponent\nand will return either the\ncomponents\nvalue (if the evaluation succeeds) or the\nstring \"failed\"\n(if the evaluation fails).\n\nThe call to\nfunctions,\nwhich continue the loop and support the\nretry\nrequest.\n\nMost of the complexity of the\nfunctions\ncall each other.\n\nIn going through the following code, you should compare\neach of the execution\nfunctions\nwith the corresponding\nfunction\nfor the ordinary evaluator given in\nsection.\n\nThe execution\nfunctions\nfor the simplest kinds of expressions are\nessentially the same as those for the ordinary evaluator, except for the\nneed to manage the continuations.\n\nThe execution\nfunctions\nsimply succeed with the value of the expression, passing along the failure\ncontinuation that was passed to them.\n\n```javascript\nanalyze_literal_amb\n all_solutions_test_4\n\nfunction analyze_literal(component) {\n return (env, succeed, fail) =>\n succeed(literal_value(component), fail);\n}\n```", - "token_count": 309, + "content": "given environment, together with two given continuations:\n\n```javascript\nanalyze_amb\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n analyze_amb_headline\n is_amb_amb\n analyze_literal_amb\n analyze_variable_amb\n analyze_lambda_amb\n analyze_sequence_amb\n analyze_declaration_amb\n analyze_assignment_amb\n analyze_if_amb\n scan_out_declarations\n analyze_block_amb\n analyze_return_statement_amb\n analyze_application_amb\n analyze_amb_amb\n\nfunction analyze(component) {\n return is_literal(component)\n ? analyze_literal(component)\n : is_name(component)\n ? analyze_name(component)\n : is_amb(component)\n ? analyze_amb(component)\n : is_application(component)\n ? analyze_application(component)\n : is_operator_combination(component)\n ? analyze(operator_combination_to_application(component))\n : is_conditional(component)\n ? analyze_conditional(component)\n : is_lambda_expression(component)\n ? analyze_lambda_expression(component)\n : is_sequence(component)\n ? analyze_sequence(sequence_statements(component))\n : is_block(component)\n ? analyze_block(component)\n : is_return_statement(component)\n ? analyze_return_statement(component)\n : is_function_declaration(component)\n ? analyze(function_decl_to_constant_decl(component))\n : is_declaration(component)\n ? analyze_declaration(component)\n : is_assignment(component)\n ? analyze_assignment(component)\n : error(component, \"unknown syntax -- analyze\");\n}\n```\n\n```javascript\nambeval\n analyze_amb\n all_solutions_test_4\n\nfunction ambeval(component, env, succeed, fail) {\n return analyze(component)(env, succeed, fail);\n}\n```\n\nA success\ncontinuation is a\nfunction\nof two arguments: the value just obtained and another failure continuation to\nbe used if that value leads to a subsequent failure.\n\nA\nfailure continuation\nis a\nfunction\nof no arguments.\n\nSo\nthe general form of an\nexecution\nfunction\nis\n\n```javascript\n(env, succeed, fail) => {\n // $\\texttt{succeed}\\,$ is $\\texttt{(value, fail) =>}~\\ldots$\n // $\\texttt{fail}\\,$ is $\\texttt{() =>}~\\ldots$\n $\\ldots$\n}\n```\n\nFor example, executing\nambeval(component, the_global_environment, (value, fail) => value, () => \"failed\");\nwill attempt to evaluate the given\ncomponent\nand will return either the\ncomponents\nvalue (if the evaluation succeeds) or the\nstring \"failed\"\n(if the evaluation fails).\n\nThe call to in the driver loop shown\nbelow uses much more complicated continuation\nfunctions,\nwhich continue the loop and support the\nretry\nrequest.\n\nMost of the complexity of the evaluator\nresults from the mechanics of passing the continuations around as the\nexecution\nfunctions\ncall each other.\n\nIn going through the following code, you should compare\neach of the execution\nfunctions\nwith the corresponding\nfunction\nfor the ordinary evaluator given in\nsection.", + "token_count": 281, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", @@ -2470,8 +3730,8 @@ "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_4" }, { - "content": "The execution\nfunctions\nsimply succeed with the value of the expression, passing along the failure\ncontinuation that was passed to them.\n\n```javascript\nanalyze_variable_amb\n all_solutions_test_4\n\nfunction analyze_name(component) {\n return (env, succeed, fail) =>\n succeed(lookup_symbol_value(symbol_of_name(component),\n env),\n fail);\n}\n```\n\n```javascript\nanalyze_lambda_amb\n all_solutions_test_4\n\nfunction analyze_lambda_expression(component) {\n const params = lambda_parameter_symbols(component);\n const bfun = analyze(lambda_body(component));\n return (env, succeed, fail) =>\n succeed(make_function(params, bfun, env),\n fail);\n}\n```\n\nNotice that looking up a\nname\nalways succeeds.\nlookup_symbol_value\nfails to find the\nname,\nit signals an\nerror, as usual.\n\nSuch a failure indicates a program\nbug a reference to an unbound\n\n```javascript\nname;\n```\n\nit is not an indication that we should try another nondeterministic choice instead of the one that is currently being tried.\n\nConditionals are also handled in a similar way as in the ordinary\nevaluator.\n\nThe execution\nfunction\ngenerated by\nanalyze_conditional\ninvokes the predicate execution\n\n```javascript\nfunction\n pfun\n```\n\nwith a success continuation that checks whether the predicate value is true\nand goes on to execute either the consequent or the alternative.\n\nIf the\nexecution of\npfun\nfails, the original failure continuation for\nthe\nconditional\nexpression is called.\n\n```javascript\nanalyze_if_amb\n all_solutions_test_4\n\nfunction analyze_conditional(component) {\n const pfun = analyze(conditional_predicate(component));\n const cfun = analyze(conditional_consequent(component));\n const afun = analyze(conditional_alternative(component));\n return (env, succeed, fail) =>\n pfun(env,\n // success continuation for evaluating the predicate\n // to obtain $\\texttt{pred\\char`_value}$\n (pred_value, fail2) =>\n is_truthy(pred_value)\n ? cfun(env, succeed, fail2)\n : afun(env, succeed, fail2),\n // failure continuation for evaluating the predicate\n fail);\n}\n```", - "token_count": 241, + "content": "In going through the following code, you should compare\neach of the execution\nfunctions\nwith the corresponding\nfunction\nfor the ordinary evaluator given in\nsection.\n\nThe execution\nfunctions\nsimply succeed with the value of the expression, passing along the failure\ncontinuation that was passed to them.\n\n```javascript\nanalyze_literal_amb\n all_solutions_test_4\n\nfunction analyze_literal(component) {\n return (env, succeed, fail) =>\n succeed(literal_value(component), fail);\n}\n```\n\n```javascript\nanalyze_variable_amb\n all_solutions_test_4\n\nfunction analyze_name(component) {\n return (env, succeed, fail) =>\n succeed(lookup_symbol_value(symbol_of_name(component),\n env),\n fail);\n}\n```\n\n```javascript\nanalyze_lambda_amb\n all_solutions_test_4\n\nfunction analyze_lambda_expression(component) {\n const params = lambda_parameter_symbols(component);\n const bfun = analyze(lambda_body(component));\n return (env, succeed, fail) =>\n succeed(make_function(params, bfun, env),\n fail);\n}\n```\n\nNotice that looking up a\nname\nalways succeeds.\n\nIf\nlookup_symbol_value\nfails to find the\nname,\nit signals an\nerror, as usual.\n\nSuch a failure indicates a program\nbug a reference to an unbound\nname;\nit is not an indication\nthat we should try another nondeterministic choice instead of the one that\nis currently being tried.\n\nConditionals are also handled in a similar way as in the ordinary\nevaluator.\n\nThe execution\nfunction\ngenerated by\nanalyze_conditional\ninvokes the predicate execution\nfunction pfun\nwith a success continuation that checks whether the predicate value is true\nand goes on to execute either the consequent or the alternative.\n\nIf the\nexecution of\npfun\nfails, the original failure continuation for\nthe\nconditional\nexpression is called.\n\n```javascript\nanalyze_if_amb\n all_solutions_test_4\n\nfunction analyze_conditional(component) {\n const pfun = analyze(conditional_predicate(component));\n const cfun = analyze(conditional_consequent(component));\n const afun = analyze(conditional_alternative(component));\n return (env, succeed, fail) =>\n pfun(env,\n // success continuation for evaluating the predicate\n // to obtain $\\texttt{pred\\char`_value}$\n (pred_value, fail2) =>\n is_truthy(pred_value)\n ? cfun(env, succeed, fail2)\n : afun(env, succeed, fail2),\n // failure continuation for evaluating the predicate\n fail);\n}\n```\n\nSequences are also handled in the same way as in the previous evaluator, except for the machinations in the subfunction that are required for passing the continuations.", + "token_count": 305, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", @@ -2480,8 +3740,8 @@ "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_5" }, { - "content": "If the\nexecution of\npfun\nfails, the original failure continuation for\nthe\nconditional\nexpression is called.\n\n```javascript\nSequences are also handled in the same way as in the previous\n\tevaluator, except for the machinations in the\n\tsubfunction\n\n\t analyze_sequence_amb\n\t all_solutions_test_4\n\nfunction analyze_sequence(stmts) {\n function sequentially(a, b) {\n return (env, succeed, fail) =>\n a(env,\n // success continuation for calling $\\texttt{a}$\n (a_value, fail2) =>\n is_return_value(a_value)\n ? succeed(a_value, fail2)\n : b(env, succeed, fail2),\n // failure continuation for calling $\\texttt{a}$\n fail);\n }\n function loop(first_fun, rest_funs) {\n return is_null(rest_funs)\n ? first_fun\n : loop(sequentially(first_fun, head(rest_funs)),\n tail(rest_funs));\n }\n const funs = map(analyze, stmts);\n return is_null(funs)\n ? env => undefined\n : loop(head(funs), tail(funs));\n}\n```\n\nDeclarations\nare another case where we must go to some trouble to\nmanage the continuations, because it is necessary to evaluate the\ndeclaration-value expression before actually declaring the new name.\n\nTo accomplish this, the\ndeclaration-value\nexecution\nfunction\nis called with the environment, a success continuation, and the\nfailure continuation.\n\nIf the execution of\nsucceeds, obtaining a value\ndeclared name, the name is declared and the success is propagated:\n\n```javascript\nanalyze_declaration_amb\n all_solutions_test_4\n\nfunction analyze_declaration(component) {\n const symbol = declaration_symbol(component);\n const vfun = analyze(declaration_value_expression(component));\n return (env, succeed, fail) =>\n vfun(env,\n (val, fail2) => {\n assign_symbol_value(symbol, val, env);\n return succeed(undefined, fail2);\n },\n fail);\n}\n```\n\nAssignments\nfunction\nfor assignments starts out like the one for\ndeclarations.\n\nIt first attempts\nto obtain the new value to be assigned to the\nname.\n\nIf this evaluation of\nvfun\nfails, the assignment fails.\n\nIf\nvfun\nsucceeds, however, and we go on to make the assignment, we must consider the\npossibility that this branch of the computation might later fail, which will\nrequire us to backtrack out of the assignment.\n\nThus, we must arrange to\nundo the assignment as part of the backtracking process.", - "token_count": 292, + "content": "Sequences are also handled in the same way as in the previous evaluator, except for the machinations in the subfunction that are required for passing the continuations.\n\nDeclarations\nare another case where we must go to some trouble to\nmanage the continuations, because it is necessary to evaluate the\ndeclaration-value expression before actually declaring the new name.\n\nTo accomplish this, the\ndeclaration-value\nexecution\nfunction\nis called with the environment, a success continuation, and the\nfailure continuation.\n\nIf the execution of\nsucceeds, obtaining a value for the\ndeclared name, the name is declared and the success is propagated:\n\n```javascript\nanalyze_declaration_amb\n all_solutions_test_4\n\nfunction analyze_declaration(component) {\n const symbol = declaration_symbol(component);\n const vfun = analyze(declaration_value_expression(component));\n return (env, succeed, fail) =>\n vfun(env,\n (val, fail2) => {\n assign_symbol_value(symbol, val, env);\n return succeed(undefined, fail2);\n },\n fail);\n}\n```\n\nAssignments\nare more interesting.\n\nThis is the first place where we\nreally use the continuations, rather than just passing them around.\n\nThe execution\nfunction\nfor assignments starts out like the one for\ndeclarations.\n\nIt first attempts\nto obtain the new value to be assigned to the\nname.\n\nIf this evaluation of\nvfun\nfails, the assignment fails.\n\nIf\nvfun\nsucceeds, however, and we go on to make the assignment, we must consider the\npossibility that this branch of the computation might later fail, which will\nrequire us to backtrack out of the assignment.\n\nThus, we must arrange to\nundo the assignment as part of the backtracking process.\n\nThis is accomplished by giving\nvfun\na success continuation (marked with the comment *1* below)\nthat saves the old value of the variable before assigning the new value to\nthe variable and proceeding from the assignment.", + "token_count": 273, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", @@ -2490,7 +3750,7 @@ "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_6" }, { - "content": "Thus, we must arrange to\nundo the assignment as part of the backtracking process.\n\nThis is accomplished by giving\nvfun\na success continuation (marked with the comment *1* below)\nthat saves the old value of the variable before assigning the new value to\nthe variable and proceeding from the assignment.\n\nThe failure continuation\nthat is passed along with the value of the assignment (marked with the\ncomment *2* below) restores the old value of the variable\nbefore continuing the failure.\n\nThat is, a successful assignment provides a\nfailure continuation that will intercept a subsequent failure; whatever\nfailure would otherwise have called\nfunction\ninstead, to undo the assignment before actually calling\n\n```javascript\nanalyze_assignment_amb\n all_solutions_test_4\n\nfunction analyze_assignment(component) {\n const symbol = assignment_symbol(component);\n const vfun = analyze(assignment_value_expression(component));\n return (env, succeed, fail) =>\n vfun(env,\n (val, fail2) => { // *1*\n const old_value = lookup_symbol_value(symbol,\n env);\n assign_symbol_value(symbol, val, env);\n return succeed(val,\n () => { // *2*\n assign_symbol_value(symbol,\n old_value,\n env);\n return fail2();\n });\n },\n fail);\n}\n```\n\nAnalyzing return statements is straightforward.\n\nThe return expression is analyzed to produce an execution function.\n\nThe execution function for the return statement calls that execution\nfunction with a success continuation that wraps the return value\nin a return value object and passes it to the original success continuation.\n\n```javascript\nanalyze_return_statement_amb\n\t all_solutions_test_4\n\nfunction analyze_return_statement(component) {\n const rfun = analyze(return_expression(component));\n return (env, succeed, fail) =>\n rfun(env,\n (val, fail2) =>\n succeed(make_return_value(val), fail2),\n fail);\n}\n```\n\nThe execution function for blocks calls the body s execution function on an extended environment, without changing success or failure continuations.\n\n```javascript\nanalyze_block_amb\n\t list_of_unassigned\n\t all_solutions_test_4\n\nfunction analyze_block(component) {\n const body = block_body(component);\n const locals = scan_out_declarations(body);\n const unassigneds = list_of_unassigned(locals);\n const bfun = analyze(body);\n return (env, succeed, fail) =>\n bfun(extend_environment(locals, unassigneds, env),\n succeed,\n fail);\n}\n```", + "content": "This is accomplished by giving\nvfun\na success continuation (marked with the comment *1* below)\nthat saves the old value of the variable before assigning the new value to\nthe variable and proceeding from the assignment.\n\nThat is, a successful assignment provides a\nfailure continuation that will intercept a subsequent failure; whatever\nfailure would otherwise have called calls\nthis\nfunction\ninstead, to undo the assignment before actually calling.\n\n```javascript\nanalyze_assignment_amb\n all_solutions_test_4\n\nfunction analyze_assignment(component) {\n const symbol = assignment_symbol(component);\n const vfun = analyze(assignment_value_expression(component));\n return (env, succeed, fail) =>\n vfun(env,\n (val, fail2) => { // *1*\n const old_value = lookup_symbol_value(symbol,\n env);\n assign_symbol_value(symbol, val, env);\n return succeed(val,\n () => { // *2*\n assign_symbol_value(symbol,\n old_value,\n env);\n return fail2();\n });\n },\n fail);\n}\n```\n\nAnalyzing return statements is straightforward.\n\nThe return expression is analyzed to produce an execution function.\n\nThe execution function for the return statement calls that execution\nfunction with a success continuation that wraps the return value\nin a return value object and passes it to the original success continuation.\n\n```javascript\nanalyze_return_statement_amb\n\t all_solutions_test_4\n\nfunction analyze_return_statement(component) {\n const rfun = analyze(return_expression(component));\n return (env, succeed, fail) =>\n rfun(env,\n (val, fail2) =>\n succeed(make_return_value(val), fail2),\n fail);\n}\n```\n\nThe execution function for blocks calls the body s execution function on an extended environment, without changing success or failure continuations.\n\n```javascript\nanalyze_block_amb\n\t list_of_unassigned\n\t all_solutions_test_4\n\nfunction analyze_block(component) {\n const body = block_body(component);\n const locals = scan_out_declarations(body);\n const unassigneds = list_of_unassigned(locals);\n const bfun = analyze(body);\n return (env, succeed, fail) =>\n bfun(extend_environment(locals, unassigneds, env),\n succeed,\n fail);\n}\n```\n\nThe execution\nfunction\nfor applications contains no new ideas except for the technical complexity\nof managing the continuations.\n\nThis complexity arises in\nanalyze_@application,\ndue to the need to keep track of the success and failure continuations as\nwe evaluate the\nargument expressions.", "token_count": 289, "has_code": true, "chapter": "Metalinguistic Abstraction", @@ -2500,8 +3760,8 @@ "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_7" }, { - "content": "The execution function for blocks calls the body s execution function on an extended environment, without changing success or failure continuations.\n\nThe execution\nfunction\nfor applications contains no new ideas except for the technical complexity\nof managing the continuations.\n\nThis complexity arises in\nanalyze_@application,\ndue to the need to keep track of the success and failure continuations as\nwe evaluate the\nargument expressions.\n\nWe use a\nfunction get_args\nto evaluate the list of\nargument expressions,\nrather than a simple\n\n```javascript\nanalyze_application_amb\n get_args_amb\n execute_application_amb\n all_solutions_test_4\n\nfunction analyze_application(component) {\n const ffun = analyze(function_expression(component));\n const afuns = map(analyze, arg_expressions(component));\n return (env, succeed, fail) =>\n ffun(env,\n (fun, fail2) =>\n get_args(afuns,\n env,\n (args, fail3) =>\n execute_application(fun,\n args,\n succeed,\n fail3),\n fail2),\n fail);\n}\n```\n\n```javascript\nIn get_args,\n\tnotice how walking down the list of\n\tafun\n\texecution functions\n\tand constructing the resulting list of\n\tafun\n\tin the list with a success continuation that recursively calls\n\tget_args.\n```\n\nEach of these recursive calls to get_args has a success continuation whose value is the\n\n```javascript\nnew list resulting from using\n\tpair\n\tto adjoin the newly obtained argument\n\tto the list of accumulated arguments:\n```\n\n```javascript\nget_args_amb\n all_solutions_test_4\n\nfunction get_args(afuns, env, succeed, fail) {\n return is_null(afuns)\n ? succeed(null, fail)\n : head(afuns)(env,\n // success continuation for this $\\texttt{afun}$\n (arg, fail2) =>\n get_args(tail(afuns),\n env,\n // success continuation for\n // recursive call to $\\texttt{get\\char`_args}$\n (args, fail3) =>\n succeed(pair(arg, args),\n fail3),\n fail2),\n fail);\n}\n```\n\nThe actual function application, which is performed by execute_application, is accomplished in the same way as for the ordinary evaluator, except for the need to\n\nmanage the continuations.\n\n```javascript\nexecute_application_amb\n all_solutions_test_4\n\nfunction execute_application(fun, args, succeed, fail) {\n return is_primitive_function(fun)\n ? succeed(apply_primitive_function(fun, args),\n fail)\n : is_compound_function(fun)\n ? function_body(fun)(\n extend_environment(function_parameters(fun),\n args,\n function_environment(fun)),\n (body_result, fail2) =>\n succeed(is_return_value(body_result)\n ? return_value_content(body_result)\n : undefined,\n fail2),\n fail)\n : error(fun, \"unknown function type - execute_application\");\n}\n```", - "token_count": 300, + "content": "This complexity arises in\nanalyze_@application,\ndue to the need to keep track of the success and failure continuations as\nwe evaluate the\nargument expressions.\n\n```javascript\nanalyze_application_amb\n get_args_amb\n execute_application_amb\n all_solutions_test_4\n\nfunction analyze_application(component) {\n const ffun = analyze(function_expression(component));\n const afuns = map(analyze, arg_expressions(component));\n return (env, succeed, fail) =>\n ffun(env,\n (fun, fail2) =>\n get_args(afuns,\n env,\n (args, fail3) =>\n execute_application(fun,\n args,\n succeed,\n fail3),\n fail2),\n fail);\n}\n```\n\nIn get_args, notice how walking down the list of afun execution functions and constructing the resulting list of is accomplished by calling each afun in the list with a success continuation that recursively calls get_args.\n\nEach of these recursive calls to\nget_args\nhas a success continuation whose value is the\nnew list resulting from using pair to adjoin the newly obtained argument to the list of accumulated arguments:\n\n```javascript\nget_args_amb\n all_solutions_test_4\n\nfunction get_args(afuns, env, succeed, fail) {\n return is_null(afuns)\n ? succeed(null, fail)\n : head(afuns)(env,\n // success continuation for this $\\texttt{afun}$\n (arg, fail2) =>\n get_args(tail(afuns),\n env,\n // success continuation for\n // recursive call to $\\texttt{get\\char`_args}$\n (args, fail3) =>\n succeed(pair(arg, args),\n fail3),\n fail2),\n fail);\n}\n```\n\nThe actual function application, which is performed by execute_application, is accomplished in the same way as for the ordinary evaluator, except for the need to\n\nmanage the continuations.\n\n```javascript\nexecute_application_amb\n all_solutions_test_4\n\nfunction execute_application(fun, args, succeed, fail) {\n return is_primitive_function(fun)\n ? succeed(apply_primitive_function(fun, args),\n fail)\n : is_compound_function(fun)\n ? function_body(fun)(\n extend_environment(function_parameters(fun),\n args,\n function_environment(fun)),\n (body_result, fail2) =>\n succeed(is_return_value(body_result)\n ? return_value_content(body_result)\n : undefined,\n fail2),\n fail)\n : error(fun, \"unknown function type - execute_application\");\n}\n```\n\nThe\nsyntactic\nform is the key element in the nondeterministic language.\n\nHere we see the\nessence of the interpretation process and the reason for keeping track of\nthe continuations.\n\nThe execution\nfunction\nfor defines a loop\ntry_next\nthat cycles through the execution\nfunctions\nfor all the possible values of the\nexpression.", + "token_count": 297, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", @@ -2510,8 +3770,8 @@ "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_8" }, { - "content": "manage the continuations.\n\nThe\nsyntactic\nform is the key element in the nondeterministic language.\n\nHere we see the\nessence of the interpretation process and the reason for keeping track of\nthe continuations.\n\nThe execution\nfunction\nfor\ntry_next\nthat cycles through the execution\nfunctions\nfor all the possible values of the\nfunction\nis called with a\n\n```javascript\nanalyze_amb_amb\n all_solutions_test_4\n\nfunction analyze_amb(component) {\n const cfuns = map(analyze, amb_choices(component));\n return (env, succeed, fail) => {\n function try_next(choices) {\n return is_null(choices)\n ? fail()\n : head(choices)(env,\n succeed,\n () =>\n try_next(tail(choices)));\n }\n return try_next(cfuns);\n };\n}\n```\n\nThe driver loop for the\na program.\n\nThe driver uses a\nfunction\ncalled\ninternal_loop,\nwhich takes as argument a\nfunction\nretry.\n\nThe intent is that calling\nretry\nshould go on to the next untried alternative in the nondeterministic\nevaluation.\n\n```javascript\nThe function\n internal_loop\n```\n\neither calls retry in response to the user typing retry at the driver loop, or else starts a new evaluation by calling\n\nThe failure continuation for this call to\n\nThe success continuation for the call to\nreinvoke the internal loop\nwith a\nretry\nfunction\nthat will be able to try the next alternative.\n\nThis\nnext_alternative\nfunction\nis the second argument that was passed to the success continuation.\n\nOrdinarily, we think of this second argument as a failure continuation to\nbe used if the current evaluation branch later fails.\n\nIn this case,\nhowever, we have completed a successful evaluation, so we can invoke the\nfailure alternative branch in order to search for additional\nsuccessful evaluations.\n\n```javascript\ndriver_loop_amb_example\n\ndriver_loop();\n```", - "token_count": 253, + "content": "The execution\nfunction\nfor defines a loop\ntry_next\nthat cycles through the execution\nfunctions\nfor all the possible values of the\nexpression.\n\nWhen\nthere are no more alternatives to try, the entire\nexpression fails.\n\n```javascript\nanalyze_amb_amb\n all_solutions_test_4\n\nfunction analyze_amb(component) {\n const cfuns = map(analyze, amb_choices(component));\n return (env, succeed, fail) => {\n function try_next(choices) {\n return is_null(choices)\n ? fail()\n : head(choices)(env,\n succeed,\n () =>\n try_next(tail(choices)));\n }\n return try_next(cfuns);\n };\n}\n```\n\nThe driver loop for the evaluator is\ncomplex, due to the mechanism that permits the user to retry in evaluating\na program.\n\nThe driver uses a\nfunction\ncalled\ninternal_loop,\nwhich takes as argument a\nfunction\nretry.\n\nThe intent is that calling\nretry\nshould go on to the next untried alternative in the nondeterministic\nevaluation.\n\nThe function internal_loop\neither calls\nretry\nin response to the user typing\nretry\nat the driver loop, or else starts a new evaluation by calling.\n\nThe failure continuation for this call to informs the user that there are no more values and reinvokes the driver loop.\n\nThe success continuation for the call to\nis more subtle.\n\nWe print the obtained value and then\nreinvoke the internal loop\nwith a\nretry\nfunction\nthat will be able to try the next alternative.\n\nThis\nnext_alternative\nfunction\nis the second argument that was passed to the success continuation.\n\nOrdinarily, we think of this second argument as a failure continuation to\nbe used if the current evaluation branch later fails.\n\nIn this case,\nhowever, we have completed a successful evaluation, so we can invoke the\nfailure alternative branch in order to search for additional\nsuccessful evaluations.\n\n```javascript\ndriver_loop_amb_example\n\ndriver_loop();\n```", + "token_count": 269, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", @@ -2520,8 +3780,8 @@ "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_9" }, { - "content": "In this case,\nhowever, we have completed a successful evaluation, so we can invoke the\nfailure alternative branch in order to search for additional\nsuccessful evaluations.\n\n```javascript\ndriver_loop_amb\n ambeval\n user_print\n user_read\n driver_loop_amb_example\n\nconst input_prompt = \"amb-evaluate input:\";\nconst output_prompt = \"amb-evaluate value:\";\n\nfunction driver_loop(env) {\n function internal_loop(retry) {\n const input = user_read(input_prompt);\n if (is_null(input)) {\n display(\"evaluator terminated\");\n } else if (input === \"retry\") {\n return retry();\n } else {\n display(\"Starting a new problem\");\n const program = parse(input);\n const locals = scan_out_declarations(program);\n const unassigneds = list_of_unassigned(locals);\n const program_env = extend_environment(\n locals, unassigneds, env);\n return ambeval(\n program,\n program_env,\n // ambeval success\n (val, next_alternative) => {\n user_print(output_prompt, val);\n return internal_loop(next_alternative);\n },\n // ambeval failure\n () => {\n display(\"There are no more values of\");\n display(input);\n return driver_loop(program_env);\n });\n }\n }\n return internal_loop(() => {\n display(\"There is no current problem\");\n return driver_loop(env);\n });\n}\n\nconst input_prompt = \"amb-evaluate input:\";\nconst output_prompt = \"amb-evaluate value:\";\nfunction driver_loop() {\n function internal_loop(retry) {\n const input = user_read(input_prompt);\n if (is_null(input)) {\n display(\"--- evaluator terminated ---\", \"\");\n } else if (input === \"retry\") {\n display(\"----------------------------\",\n input_prompt + \"\\n\" + input + \"\\n\");\n return retry();\n } else {\n display(\"--- starting new problem ---\",\n input_prompt + \"\\n\" + input + \"\\n\");\n ambeval(parse(\"{ \" + input + \" }\"),\n the_global_environment,\n // ambeval success\n (val, next_alternative) => {\n user_print(output_prompt, val);\n return internal_loop(next_alternative);\n },\n // ambeval failure\n () => {\n display(\"----------------------------\",\n \"no more values of:\\n\" + input + \"\\n\");\n return driver_loop();\n });\n }\n }\n return internal_loop(\n () => {\n display(\"--- no current problem ---\", \"\");\n return driver_loop();\n });\n}\n```\n\nThe initial call to\ninternal_loop\nuses a\nretry\nfunction\nthat complains that there is no current problem and restarts the driver loop.\n\nThis is the behavior that will happen if the user types\nretry\nwhen there is no evaluation in progress.", - "token_count": 296, + "content": "In this case,\nhowever, we have completed a successful evaluation, so we can invoke the\nfailure alternative branch in order to search for additional\nsuccessful evaluations.\n\nThe initial call to\ninternal_loop\nuses a\nretry\nfunction\nthat complains that there is no current problem and restarts the driver loop.\n\nThis is the behavior that will happen if the user types\nretry\nwhen there is no evaluation in progress.\n\nWe start the driver loop as usual, by setting up the global environment and passing it as the enclosing environment for the first iteration of\n\ndriver_loop.\n\n```javascript\nconst the_global_environment = setup_environment();\ndriver_loop(the_global_environment);\n```", + "token_count": 100, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", @@ -2530,18 +3790,8 @@ "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_10" }, { - "content": "This is the behavior that will happen if the user types\nretry\nwhen there is no evaluation in progress.\n\nWe start the driver loop as usual, by setting up the global environment and passing it as the enclosing environment for the first iteration of\n\ndriver_loop.\n\n```javascript\nconst the_global_environment = setup_environment();\ndriver_loop(the_global_environment);\n```", - "token_count": 52, - "has_code": true, - "chapter": "Metalinguistic Abstraction", - "section": "Nondeterministic Computing", - "subsection": "Implementing the amb Evaluator", - "chunk_index": 11, - "chunk_id": "Metalinguistic_Abstraction_Implementing_the_amb_Evaluator_11" - }, - { - "content": "To extend\nJavaScript\nto support nondeterminism, we introduce a new\nsyntactic form\namb($e_1,\\ e_2,\\ldots, e_n$)\nreturns the value of one of the $n$ expressions\n$e_i$ ambiguously.\n\nFor example,\nthe expression\n\n```javascript\nlist_non_det\n\nlist(amb(1, 2, 3), amb(\"a\", \"b\"));\n\nlist(amb(1, 2, 3), amb(\"a\", \"b\"));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\ncan have six possible values:\n\n```javascript\nlist(1, \"a\") list(1, \"b\") list(2, \"a\")\nlist(2, \"b\") list(3, \"a\") list(3, \"b\")\n```\n\nAn amb expression with a single choice produces an ordinary (single) value.\n\nAn amb expression\nwith no choices the expression\namb()is\namb()\nas an expression that when evaluated causes the computation to\nfail : The computation aborts and no value is produced.\n\nUsing this idea, we can express the requirement that a particular predicate\nexpression\n\n```javascript\nrequire_non_det_example\n\nconst x = amb(1, 3, 5, 7, 9);\nrequire(x >= 4);\nx;\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\nrequire_non_det\n require_non_det_example\n 5\n\nfunction require(p) {\n if (! p) {\n amb();\n } else {}\n}\n```\n\nWith an_element_of function used above:\n\n```javascript\nan_element_of_example\n\nconst xs = list(\"apple\", \"banana\", \"cranberry\");\nan_element_of(xs);\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\nan_element_of\n an_element_of_example\n 'apple'\n\nfunction an_element_of(items) {\n require(! is_null(items));\n return amb(head(items), an_element_of(tail(items)));\n}\n```\n\nAn application of an_element_of\nfails if the list is empty.\n\nOtherwise it ambiguously returns either the\nfirst element of the list or an element chosen from the rest of the list.\n\nWe can also express infinite ranges of choices.\n\nThe following\nfunction\npotentially returns any integer greater than or equal to some\ngiven $n$ :", - "token_count": 293, + "content": "To extend\nJavaScript\nto support nondeterminism, we introduce a new\nsyntactic form\ncalled.\n\nThe expression\namb($e_1,\\ e_2,\\ldots, e_n$)\nreturns the value of one of the $n$ expressions\n$e_i$ ambiguously.\n\nFor example,\nthe expression\n\n```javascript\nlist_non_det\n\nlist(amb(1, 2, 3), amb(\"a\", \"b\"));\n\nlist(amb(1, 2, 3), amb(\"a\", \"b\"));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\ncan have six possible values:\n\n```javascript\nlist(1, \"a\") list(1, \"b\") list(2, \"a\")\nlist(2, \"b\") list(3, \"a\") list(3, \"b\")\n```\n\nAn amb expression with a single choice produces an ordinary (single) value.\n\nAn amb expression\nwith no choices the expression\namb()is\nan expression with no acceptable values.\n\nOperationally, we can think of\namb()\nas an expression that when evaluated causes the computation to\nfail : The computation aborts and no value is produced.\n\nUsing this idea, we can express the requirement that a particular predicate\nexpression must be true as follows:\n\n```javascript\nrequire_non_det_example\n\nconst x = amb(1, 3, 5, 7, 9);\nrequire(x >= 4);\nx;\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\nrequire_non_det\n require_non_det_example\n 5\n\nfunction require(p) {\n if (! p) {\n amb();\n } else {}\n}\n```\n\nWith and , we can implement the an_element_of function used above:\n\n```javascript\nan_element_of_example\n\nconst xs = list(\"apple\", \"banana\", \"cranberry\");\nan_element_of(xs);\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\nan_element_of\n an_element_of_example\n 'apple'\n\nfunction an_element_of(items) {\n require(! is_null(items));\n return amb(head(items), an_element_of(tail(items)));\n}\n```\n\nAn application of an_element_of\nfails if the list is empty.\n\nOtherwise it ambiguously returns either the\nfirst element of the list or an element chosen from the rest of the list.\n\nWe can also express infinite ranges of choices.", + "token_count": 302, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", @@ -2550,8 +3800,8 @@ "chunk_id": "Metalinguistic_Abstraction_Search_and_amb_1" }, { - "content": "The following\nfunction\npotentially returns any integer greater than or equal to some\ngiven $n$ :\n\n```javascript\nan_integer_starting_from_example\n\nconst x = an_integer_starting_from(1);\nrequire(x >= 4.5);\nx;\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\nan_integer_starting_from\n an_integer_starting_from_example\n 5\n\nfunction an_integer_starting_from(n) {\n return amb(n, an_integer_starting_from(n + 1));\n}\n```\n\nThis is like the stream\n\n```javascript\nfunction\n integers_starting_from\n```\n\ndescribed in section , but with an important difference: The stream function returns an object that represents the sequence of all integers beginning with $n$\n\n, whereas the function returns a single integer.\n\nAbstractly, we can imagine that evaluating an\nnondeterministic choice point.\n\nIf we had a machine with a\nsufficient number of processors that could be dynamically allocated, we\ncould implement the search in a straightforward way.\n\nExecution would\nproceed as in a sequential machine, until an\n\nOn the other hand, if we have a machine that can execute only one process\n(or a few concurrent processes), we must consider the alternatives\nsystematically search all possible execution paths.\n\nThe\nbacktracks to the most recent choice point and tries the next\nalternative.\n\nIf it runs out of alternatives at any choice point, the\nevaluator will back up to the previous choice point and resume from there.\n\nThis process leads to a search strategy known as\ndepth-first search or\nchronological\nbacktracking.\n\nThe\na program\nand prints the value of the\nfirst non-failing execution, as in the\nprime_sum_pair\nexample shown above.\n\nIf we want to see the value of the next successful\nexecution, we can ask the interpreter to backtrack and attempt to generate a\nsecond non-failing execution.\n\n```javascript\nThis is signaled by typing\n\tretry.\n\tIf any other input except retry\n\tis given, the interpreter will start a new problem, discarding the\n\tunexplored alternatives in the previous problem.\n```", - "token_count": 307, + "content": "We can also express infinite ranges of choices.\n\n```javascript\nan_integer_starting_from_example\n\nconst x = an_integer_starting_from(1);\nrequire(x >= 4.5);\nx;\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\nan_integer_starting_from\n an_integer_starting_from_example\n 5\n\nfunction an_integer_starting_from(n) {\n return amb(n, an_integer_starting_from(n + 1));\n}\n```\n\nThis is like the stream function integers_starting_from described in section , but with an important difference: The stream function returns an object that represents the\n\nsequence of all integers beginning with $n$ , whereas the function returns a single integer.\n\nAbstractly, we can imagine that evaluating an\nexpression causes\ntime to split into\nbranches, where the computation continues on each branch with one of the\npossible values of the expression.\n\nWe say that\nrepresents a\nnondeterministic choice point.\n\nIf we had a machine with a\nsufficient number of processors that could be dynamically allocated, we\ncould implement the search in a straightforward way.\n\nExecution would\nproceed as in a sequential machine, until an\nexpression is encountered.\n\nAt this point, more processors would be allocated\nand initialized to continue all of the parallel executions implied by the\nchoice.\n\nEach processor would proceed sequentially as if it were the only\nchoice, until it either terminates by encountering a failure, or it further\nsubdivides, or it finishes.\n\nOn the other hand, if we have a machine that can execute only one process\n(or a few concurrent processes), we must consider the alternatives\nsequentially.\n\nOne could imagine modifying an evaluator to pick at random a\nbranch to follow whenever it encounters a choice point.\n\nRandom choice,\nhowever, can easily lead to failing values.\n\nWe might try running the\nevaluator over and over, making random choices and hoping to find a\nnon-failing value, but it is better to\nsystematically search all possible execution paths.", + "token_count": 302, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", @@ -2560,8 +3810,8 @@ "chunk_id": "Metalinguistic_Abstraction_Search_and_amb_2" }, { - "content": "If we want to see the value of the next successful\nexecution, we can ask the interpreter to backtrack and attempt to generate a\nsecond non-failing execution.\n\nHere is a sample interaction:\n\n```javascript\ninteraction_non_det\n is_prime2\n prime_sum_pair_non_det\n\namb-evaluate input:\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\ninteraction_non_det_2\n is_prime2\n prime_sum_pair_non_det\n\namb-evaluate input:\n\nretry\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\ninteraction_non_det_3\n is_prime2\n prime_sum_pair_non_det\n\namb-evaluate input:\n\nretry\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\ninteraction_non_det_4\n is_prime2\n prime_sum_pair_non_det\n\namb-evaluate input:\n\nretry\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\ninteraction_non_det_5\n is_prime2\n prime_sum_pair_non_det\n\namb-evaluate input:\n\nprime_sum_pair(list(19, 27, 30), list(11, 36, 58));\n\nprime_sum_pair(list(19, 27, 30), list(11, 36, 58));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```", - "token_count": 217, + "content": "We might try running the\nevaluator over and over, making random choices and hoping to find a\nnon-failing value, but it is better to\nsystematically search all possible execution paths.\n\nThis selection may itself lead to\na further choice.\n\nThe evaluator will always initially choose the first\nalternative at each choice point.\n\nIf a choice results in a failure, then\nthe evaluator\nautomagically\nbacktracks to the most recent choice point and tries the next\nalternative.\n\nIf it runs out of alternatives at any choice point, the\nevaluator will back up to the previous choice point and resume from there.\n\nThis process leads to a search strategy known as\ndepth-first search or\nchronological\nbacktracking.\n\nThe\ndriver loop for the evaluator has some\nunusual properties.\n\nIt reads\na program\nand prints the value of the\nfirst non-failing execution, as in the\nprime_sum_pair\nexample shown above.\n\nIf we want to see the value of the next successful\nexecution, we can ask the interpreter to backtrack and attempt to generate a\nsecond non-failing execution.\n\nThis is signaled by typing retry.\n\nIf any other input except retry is given, the interpreter will start a new problem, discarding the unexplored alternatives in the previous problem.\n\nHere is a sample interaction:\n\n```javascript\ninteraction_non_det\n is_prime2\n prime_sum_pair_non_det\n\namb-evaluate input:\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n\nStarting a new problem\namb-evaluate value:\n[3, [20, null]]\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\ninteraction_non_det_2\n is_prime2\n prime_sum_pair_non_det\n\namb-evaluate input:\n\nretry\n\namb-evaluate value:\n[3, [110, null]]\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```", + "token_count": 294, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "Nondeterministic Computing", @@ -2570,19 +3820,29 @@ "chunk_id": "Metalinguistic_Abstraction_Search_and_amb_3" }, { - "content": "Our evaluator for\nJavaScript\nwill be implemented as a\nJavaScript\nprogram.\n\nIt may\nseem circular to think about evaluating\nJavaScript\nprograms using an evaluator that is itself implemented in\nJavaScript.\n\nHowever, evaluation is a process, so it is appropriate to describe the\nevaluation process using\nJavaScript,\nwhich, after all, is our tool for describing processes. metacircular.\n\nThe metacircular evaluator is essentially a JavaScript formulation of the.\n\n```javascript\nRecall that the model specifies the\n\tevaluation of function application in two basic steps:\n```\n\n- -\n\n```javascript\nTo evaluate a function application, evaluate the\n\t subexpressions and then apply the\n\t value of the function subexpression to the values of the argument\n subexpressions.\n```\n\n-\n-\nTo apply a compound\nfunction\nto a set of arguments, evaluate the\nbody of the\nfunction\nin a new environment.\n\nTo construct this\nenvironment, extend the environment part of the\nfunction\nobject by a\nframe in which the\nparameters of the\nfunction\nare bound to\nthe arguments to which the\nfunction\nis applied.\n\nThese two rules describe the essence of the evaluation process, a basic cycle in which statements and expressions to be evaluated in environments are reduced\n\nto functions to be applied to arguments, which in turn are reduced to new statements and expressions to be evaluated in new environments, and so\n\non, until we get down to names, whose values are looked up in the environment, and to operators and primitive functions, which are applied directly\n\n(see figure ). functions in the evaluator, and (see figure ).", - "token_count": 251, + "content": "Here is a sample interaction:\n\n```javascript\ninteraction_non_det_4\n is_prime2\n prime_sum_pair_non_det\n\namb-evaluate input:\n\nretry\n\nThere are no more values of\nprime_sum_pair([1, [3, [5, [8, null]]]], [20, [35, [110, null]]])\n\nprime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```\n\n```javascript\ninteraction_non_det_5\n is_prime2\n prime_sum_pair_non_det\n\namb-evaluate input:\n\nprime_sum_pair(list(19, 27, 30), list(11, 36, 58));\n\nStarting a new problem\namb-evaluate value:\n[30, [11, null]]\n\nprime_sum_pair(list(19, 27, 30), list(11, 36, 58));\n// Press \"Run\" for the first solution. Type\n// retry\n// in the REPL on the right, for more solutions\n```", + "token_count": 103, "has_code": true, "chapter": "Metalinguistic Abstraction", + "section": "Nondeterministic Computing", + "subsection": "Search and amb", + "chunk_index": 4, + "chunk_id": "Metalinguistic_Abstraction_Search_and_amb_4" + }, + { + "content": "Our evaluator for\nJavaScript\nwill be implemented as a\nJavaScript\nprogram.\n\nIt may\nseem circular to think about evaluating\nJavaScript\nprograms using an evaluator that is itself implemented in\nJavaScript.\n\nHowever, evaluation is a process, so it is appropriate to describe the\nevaluation process using\nJavaScript,\nwhich, after all, is our tool for describing processes.\n\nAn evaluator that is written in the same language\nthat it evaluates is said to be\nmetacircular.\n\nThe metacircular evaluator is essentially a\nJavaScript\nformulation of the\nenvironment model of evaluation described in\nsection.\n\nRecall that the model specifies the evaluation of function application in two basic steps:\n\n-\n-\nTo evaluate a function application, evaluate the subexpressions and then apply the value of the function subexpression to the values of the argument subexpressions.\n-\n-\nTo apply a compound\nfunction\nto a set of arguments, evaluate the\nbody of the\nfunction\nin a new environment.\n\nTo construct this\nenvironment, extend the environment part of the\nfunction\nobject by a\nframe in which the\nparameters of the\nfunction\nare bound to\nthe arguments to which the\nfunction\nis applied.\n\nThese two rules describe the essence of the evaluation process, a basic\ncycle in which\nstatements and\nexpressions to be evaluated in environments are reduced to\nfunctions\nto be applied to arguments, which in turn are reduced to new\nstatements and\nexpressions to be evaluated in new environments, and so on, until we get\ndown to\nnames,\nwhose values are looked up in the environment, and to\noperators and primitive functions,\nwhich are applied directly (see\nfigure ).\n\nThis evaluation cycle will be embodied by the interplay between the two\ncritical\nfunctions\nin the evaluator,\nand , which are described in\nsection\n(see figure ).", + "token_count": 289, + "has_code": false, + "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", "subsection": null, "chunk_index": 1, "chunk_id": "Metalinguistic_Abstraction_The_Metacircular_Evaluator_1" }, { - "content": "(see figure ). functions in the evaluator, and (see figure ).\n\n```javascript\nThe implementation of the evaluator will depend upon functions that\n\tdefine the syntax of the statements and\n\texpressions to be evaluated.\n\tWe will use\n\t=, we use an abstract predicate\n\tis_assignment to test for an\n\tassignment, and we use abstract\n\tselectors assignment_symbol and\n\tassignment_value_expression to\n\taccess the parts of an assignment.\n\tThe data abstraction layers presented in\n\tsection\n\twill allow the evaluator to remain independent of concrete syntactic\n\tissues, such as the keywords of the interpreted language, and of the\n\tchoice of data structures that represent the program components.\n```\n\nThere are also\noperations, described in\nsection , that specify the\nrepresentation of\nfunctions\nand environments.\n\nFor example,\nmake_function\nconstructs compound\nfunctions,\nlookup_symbol_value\naccesses the values of\nnames,\nand\napply_primitive_function\napplies a primitive\nfunction\nto a given list of arguments.", - "token_count": 142, - "has_code": true, + "content": "This evaluation cycle will be embodied by the interplay between the two\ncritical\nfunctions\nin the evaluator,\nand , which are described in\nsection\n(see figure ).\n\nWe will use data abstraction to make the evaluator independent of the representation of the language.\n\nFor example, rather than committing to a choice that an assignment is to be represented by a string beginning with a name followed by =, we use an abstract predicate is_assignment to test for an assignment, and we use abstract selectors assignment_symbol and assignment_value_expression to access the parts of an assignment.\n\nThe data abstraction layers presented in section will allow the evaluator to remain independent of concrete syntactic issues, such as the keywords of the interpreted language, and of the choice of data structures that represent the program components.\n\nThere are also\noperations, described in\nsection , that specify the\nrepresentation of\nfunctions\nand environments.\n\nFor example,\nmake_function\nconstructs compound\nfunctions,\nlookup_symbol_value\naccesses the values of\nnames,\nand\napply_primitive_function\napplies a primitive\nfunction\nto a given list of arguments.", + "token_count": 172, + "has_code": false, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", "subsection": null, @@ -2590,8 +3850,8 @@ "chunk_id": "Metalinguistic_Abstraction_The_Metacircular_Evaluator_2" }, { - "content": "In thinking about a\nJavaScript\nprogram that evaluates\nJavaScript statements and\nexpressions, an analogy might be helpful.\n\nOne operational view of the\nmeaning of a program is that a\n\n```javascript\nfactorial_4_1_5\n factorial_example\n 120\n\nfunction factorial(n) {\n return n === 1\n ? 1\n : factorial(n - 1) * n;\n}\n```\n\nWe may regard this program as the description of a is a flow diagram for the factorial machine, showing how the parts are wired together.\n\nIn a similar way, we can regard the evaluator as a very special\nfigure,\nthe evaluator will be able to compute factorials.\n\nThe evaluator emulating a factorial machine.\n\nFrom this perspective, our evaluator is seen to be a\nuniversal machine.\n\nIt mimics other machines when these are described as\nJavaScript\nprograms.\n\nAnother striking aspect of the evaluator is that it acts as a bridge between\nthe data objects that are manipulated by our programming language and the\nprogramming language itself.\n\nImagine that the evaluator program\n(implemented in JavaScript)\nis running, and that a user is typing\nprograms\nto the evaluator and\nobserving the results.\n\nFrom the perspective of the user, an input\nprogram\nsuch as\n\n```javascript\nx * x;\n```\n\nis a program in the programming language, which the evaluator should execute.\n\n```javascript\nFrom the perspective of the evaluator, however, the program is simply\n\ta string orafter parsinga tagged-list representation\n\tthat is to be manipulated according to a well-defined set of rules.\n```\n\nThat the\ns programs are the evaluator s data need not\nbe a source of confusion.\n\nIn fact, it is sometimes convenient to ignore\nthis distinction, and to give the user the ability to explicitly\nevaluate a string as a JavaScript statement, using JavaScript's\nprimitive function provided that it is syntactically correct evaluates the\nresulting representation in the environment in which\n\n```javascript\neval(\"5 * 5;\");\n```\n\nand", - "token_count": 305, + "content": "In thinking about a\nJavaScript\nprogram that evaluates\nJavaScript statements and\nexpressions, an analogy might be helpful.\n\nOne operational view of the\nmeaning of a program is that a\nprogram is a description of an abstract (perhaps infinitely large) machine.\n\nFor example, consider the familiar program to compute factorials:\n\n```javascript\nfactorial_4_1_5\n factorial_example\n 120\n\nfunction factorial(n) {\n return n === 1\n ? 1\n : factorial(n - 1) * n;\n}\n```\n\nWe may regard this program as the description of a\nmachine containing\nparts that decrement, multiply, and test for equality, together with a\ntwo-position switch and another factorial machine.\n\n(The factorial\nmachine is infinite because it contains another factorial machine\nwithin it.) Figure is a flow\ndiagram for the factorial machine, showing how the parts are wired together.\n\nIn a similar way, we can regard the evaluator as a very special\nmachine that takes as input a description of a machine.\n\nGiven this\ninput, the evaluator configures itself to emulate the machine\ndescribed.\n\nFor example, if we feed our evaluator the definition of\n, as shown in\nfigure,\nthe evaluator will be able to compute factorials.\n\nThe evaluator emulating a factorial machine.\n\nFrom this perspective, our evaluator is seen to be a\nuniversal machine.\n\nIt mimics other machines when these are described as\nJavaScript\nprograms.\n\nThis is striking.\n\nTry to imagine an analogous evaluator for electrical\ncircuits.\n\nThis would be a circuit that takes as input a signal encoding the\nplans for some other circuit, such as a filter.\n\nGiven this input, the\ncircuit evaluator would then behave like a filter with the same description.\n\nSuch a universal electrical circuit is almost unimaginably complex.\n\nIt is\nremarkable that the program evaluator is a rather simple\nprogram.", + "token_count": 286, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2600,8 +3860,8 @@ "chunk_id": "Metalinguistic_Abstraction_Data_as_Programs_1" }, { - "content": "and\n\n```javascript\nevaluate_example_4_1_5\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\t 25\n\nevaluate(parse(\"5 * 5;\"), the_global_environment);\n```\n\nwill both return 25.", - "token_count": 17, + "content": "It is\nremarkable that the program evaluator is a rather simple\nprogram.\n\nImagine that the evaluator program\n(implemented in JavaScript)\nis running, and that a user is typing\nprograms\nto the evaluator and\nobserving the results.\n\nFrom the perspective of the user, an input\nprogram\nsuch as\nx * x;\nis\na program\nin the programming language, which the evaluator should\nexecute.\n\nFrom the perspective of the evaluator, however, the program is simply a string orafter parsinga tagged-list representation that is to be manipulated according to a well-defined set of rules.\n\nThat the\nuser s programs are the evaluator s data need not\nbe a source of confusion.\n\nIn fact, it is sometimes convenient to ignore\nthis distinction, and to give the user the ability to explicitly\nevaluate a string as a JavaScript statement, using JavaScript's\nprimitive function\nthat takes as argument a string.\n\nIt parses the string\nand provided that it is syntactically correct evaluates the\nresulting representation in the environment in which\nis applied.\n\nThus,\n\n```javascript\neval(\"5 * 5;\");\n```\n\nand\n\n```javascript\nevaluate_example_4_1_5\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\t 25\n\nevaluate(parse(\"5 * 5;\"), the_global_environment);\n```\n\nwill both return 25.", + "token_count": 189, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2610,8 +3870,8 @@ "chunk_id": "Metalinguistic_Abstraction_Data_as_Programs_2" }, { - "content": "In addition to defining the representation of components, the evaluator implementation must also define the data structures that the evaluator manipulates internally, as part of\n\nthe execution of a program, such as the representation of functions and environments and the representation of true and false.\n\n```javascript\nIn order to limit the predicate expressions of conditionals to proper\n\tpredicates (expressions that evaluate to a boolean value) as we do throughout\n\tthis book, we insist here that the function\n\tis_truthy gets applied only to\n\tboolean values, and we accept only the boolean value\n\ttrue to be truthy.\n The opposite of\n is_truthy is called\n is_falsy.\n```\n\n```javascript\nheadline_4_1_3\n\n// functions from SICP JS 4.1.3\n```\n\n```javascript\ntrue\n true_example\n false\n\nfunction is_truthy(x) {\n return is_boolean(x)\n ? x\n : error(x, \"boolean expected, received\");\n}\nfunction is_falsy(x) { return ! is_truthy(x); }\n```\n\n```javascript\ntrue_example\n\nis_truthy(false); // should return false because only true is truthy\n```\n\nTo handle primitives, we assume that we have available the following\nfunctions:\n-\n-\napply_primitive_function(fun, args)\napplies the given primitive\nfunction\nto the argument values in the list args and returns the result of\nthe application.\n-\n-\nis_primitive_function(fun)\ntests whether\nfun\nis a primitive\nfunction.\n\nThese mechanisms for handling primitives are further described in\nsection.\n\nCompound functions are constructed from parameters, function bodies, and environments using the constructor make_function:\n\n```javascript\nmake_procedure\n tagged_list\n make_procedure_example\n [ 'x', [ 'y', null ] ]\n\nfunction make_function(parameters, body, env) {\n return list(\"compound_function\", parameters, body, env);\n}\nfunction is_compound_function(f) {\n return is_tagged_list(f, \"compound_function\");\n}\nfunction function_parameters(f) { return list_ref(f, 1); }\n\nfunction function_body(f) { return list_ref(f, 2); }\n\nfunction function_environment(f) { return list_ref(f, 3); }\n```\n\n```javascript\nmake_procedure_example\n enclosing_environment\n\nconst my_function =\n make_function(\n list(\"x\", \"y\"),\n list(\"return_statement\", parse(\"x + y;\")),\n the_empty_environment);\ndisplay(is_compound_function(my_function));\ndisplay(function_parameters(my_function));\ndisplay(function_body(my_function));\ndisplay(function_environment(my_function));\n\nconst my_function =\n make_function(\n list(\"x\", \"y\"),\n list(\"return_statement\", parse(\"x + y;\")),\n the_empty_environment);\nis_compound_function(my_function);\nfunction_body(my_function);\nfunction_environment(my_function);\nfunction_parameters(my_function);\n```", - "token_count": 305, + "content": "In addition to defining the representation of components, the evaluator implementation must also define the data structures that the evaluator manipulates internally, as part of\n\nthe execution of a program, such as the representation of functions and environments and the representation of true and false.\n\nIn order to limit the predicate expressions of conditionals to proper predicates (expressions that evaluate to a boolean value) as we do throughout this book, we insist here that the function is_truthy gets applied only to boolean values, and we accept only the boolean value true to be truthy.\n\nThe opposite of is_truthy is called is_falsy.\n\n```javascript\nheadline_4_1_3\n\n// functions from SICP JS 4.1.3\n```\n\n```javascript\ntrue\n true_example\n false\n\nfunction is_truthy(x) {\n return is_boolean(x)\n ? x\n : error(x, \"boolean expected, received\");\n}\nfunction is_falsy(x) { return ! is_truthy(x); }\n```\n\n```javascript\ntrue_example\n\nis_truthy(false); // should return false because only true is truthy\n```\n\nTo handle primitives, we assume that we have available the following\nfunctions:\n-\n-\napply_primitive_function(fun, args)\napplies the given primitive\nfunction\nto the argument values in the list args and returns the result of\nthe application.\n-\n-\nis_primitive_function(fun)\ntests whether\nfun\nis a primitive\nfunction.\n\nThese mechanisms for handling primitives are further described in\nsection.\n\nCompound functions are constructed from parameters, function bodies, and environments using the constructor make_function:\n\n```javascript\nmake_procedure\n tagged_list\n make_procedure_example\n [ 'x', [ 'y', null ] ]\n\nfunction make_function(parameters, body, env) {\n return list(\"compound_function\", parameters, body, env);\n}\nfunction is_compound_function(f) {\n return is_tagged_list(f, \"compound_function\");\n}\nfunction function_parameters(f) { return list_ref(f, 1); }\n\nfunction function_body(f) { return list_ref(f, 2); }\n\nfunction function_environment(f) { return list_ref(f, 3); }\n```\n\n```javascript\nmake_procedure_example\n enclosing_environment\n\nconst my_function =\n make_function(\n list(\"x\", \"y\"),\n list(\"return_statement\", parse(\"x + y;\")),\n the_empty_environment);\ndisplay(is_compound_function(my_function));\ndisplay(function_parameters(my_function));\ndisplay(function_body(my_function));\ndisplay(function_environment(my_function));\n\nconst my_function =\n make_function(\n list(\"x\", \"y\"),\n list(\"return_statement\", parse(\"x + y;\")),\n the_empty_environment);\nis_compound_function(my_function);\nfunction_body(my_function);\nfunction_environment(my_function);\nfunction_parameters(my_function);\n```", + "token_count": 303, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2620,8 +3880,8 @@ "chunk_id": "Metalinguistic_Abstraction_Evaluator_Data_Structures_1" }, { - "content": "Compound functions are constructed from parameters, function bodies, and environments using the constructor make_function:\n\nWe saw in section that the\nevaluation of a sequence terminates when a return statement\nis encountered, and that the evaluation of a function application needs\nto return the value undefined if\nthe evaluation of the function body does not encounter a\nreturn statement.\n\nIn order to recognize that a value resulted from a\nreturn values as evaluator data\nstructures.\n\n```javascript\nreturn_value\n\t tagged_list\n\t return_value_example\n\t 42\n\nfunction make_return_value(content) {\n return list(\"return_value\", content);\n}\nfunction is_return_value(value) {\n return is_tagged_list(value, \"return_value\");\n}\nfunction return_value_content(value) {\n return head(tail(value));\n}\n```\n\n```javascript\nreturn_value_example\n\t enclosing_environment\n\nconst my_return_value = make_return_value(42);\ndisplay(is_return_value(my_return_value));\ndisplay(return_value_content(my_return_value));\n\nconst my_return_value = make_return_value(42);\nis_return_value(my_return_value);\nreturn_value_content(my_return_value);\n```\n\nThe evaluator needs operations for\n, an environment is a\nsequence of frames, where each frame is a table of bindings that associate\nsymbols\nwith their corresponding values.\n\nWe use the following operations for\nmanipulating environments:\n\n```javascript\nlookup_symbol_value(symbol, env)\n\n\t returns the value that is bound to\n\t symbol in the environment\n\t env, or signals an error if\n\t symbol is unbound.\n\n\t extend_environment(symbols, values, base-env)\n\n\t returns a new environment, consisting of a new frame in which the\n\t symbols in the list symbols\n\t are bound to the corresponding elements in the list\n\t values, where the enclosing\n\t environment is the environment\n\t base-env.\n\n\t assign_symbol_value(symbol, value, env)\n\n\t finds the innermost frame of\n\t env\n\t in which symbol\n\t is bound, and changes that frame\n\t so that\n\t symbol\n\t is now bound to\n\t value, or signals an\n\t error if symbol is\n\t unbound.\n```\n\nTo implement these operations we\ntail\nof the list.\n\nThe empty environment is simply the empty list.\n\n```javascript\nenclosing_environment\n enclosing_environment_example\n null\n\nfunction enclosing_environment(env) { return tail(env); }\n\nfunction first_frame(env) { return head(env); }\n\nconst the_empty_environment = null;\n```\n\n```javascript\nenclosing_environment_example\n\nthe_empty_environment;\n```", - "token_count": 290, + "content": "Compound functions are constructed from parameters, function bodies, and environments using the constructor make_function:\n\nIn order to recognize that a value resulted from a\nreturn statement, we introduce return values as evaluator data\nstructures.\n\n```javascript\nreturn_value\n\t tagged_list\n\t return_value_example\n\t 42\n\nfunction make_return_value(content) {\n return list(\"return_value\", content);\n}\nfunction is_return_value(value) {\n return is_tagged_list(value, \"return_value\");\n}\nfunction return_value_content(value) {\n return head(tail(value));\n}\n```\n\n```javascript\nreturn_value_example\n\t enclosing_environment\n\nconst my_return_value = make_return_value(42);\ndisplay(is_return_value(my_return_value));\ndisplay(return_value_content(my_return_value));\n\nconst my_return_value = make_return_value(42);\nis_return_value(my_return_value);\nreturn_value_content(my_return_value);\n```\n\nThe evaluator needs operations for\nmanipulating environments.\n\nAs explained\nin section , an environment is a\nsequence of frames, where each frame is a table of bindings that associate\nsymbols\nwith their corresponding values.\n\nWe use the following operations for\nmanipulating environments:\nlookup_symbol_value(symbol, env) returns the value that is bound to symbol in the environment env, or signals an error if symbol is unbound. extend_environment(symbols, values, base-env) returns a new environment, consisting of a new frame in which the symbols in the list symbols are bound to the corresponding elements in the list values, where the enclosing environment is the environment base-env. assign_symbol_value(symbol, value, env) finds the innermost frame of env in which symbol is bound, and changes that frame so that symbol is now bound to value, or signals an error if symbol is unbound.\n\nTo implement these operations we\nrepresent an environment as a list of\nframes.\n\nThe enclosing environment of an environment is the\ntail\nof the list.\n\nThe empty environment is simply the empty list.\n\n```javascript\nenclosing_environment\n enclosing_environment_example\n null\n\nfunction enclosing_environment(env) { return tail(env); }\n\nfunction first_frame(env) { return head(env); }\n\nconst the_empty_environment = null;\n```\n\n```javascript\nenclosing_environment_example\n\nthe_empty_environment;\n```\n\nEach frame of an environment is represented as a pair of lists: a list of the names bound in that frame and a list of\n\nthe associated values.\n\n```javascript\nmake_frame_example\n\nconst my_frame = make_frame(list(\"x\", \"y\"), list(1, 2));\nframe_symbols(my_frame);\n```", + "token_count": 309, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2630,8 +3890,8 @@ "chunk_id": "Metalinguistic_Abstraction_Evaluator_Data_Structures_2" }, { - "content": "The empty environment is simply the empty list.\n\nEach frame of an environment is represented as a pair of lists: a list of the names bound in that frame and a list of\n\nthe associated values.\n\n```javascript\nmake_frame_example\n\nconst my_frame = make_frame(list(\"x\", \"y\"), list(1, 2));\nframe_symbols(my_frame);\n```\n\n```javascript\nmake_frame\n make_frame_example\n [ 'x', [ 'y', null ] ]\n\nfunction make_frame(symbols, values) { return pair(symbols, values); }\n\nfunction frame_symbols(frame) { return head(frame); }\n\nfunction frame_values(frame) { return tail(frame); }\n```\n\nTo extend an environment by a new frame that associates\nsymbols\nwith values, we make a frame consisting of the list of\nsymbols\nand the list of values, and we adjoin this to the environment.\n\nWe signal\nan error if the number of\nsymbols\ndoes not match the number of values.\n\n```javascript\nextend_environment\n extend_environment_example\n [ 1, [ 2, [ 3, null ] ] ]\n\nfunction extend_environment(symbols, vals, base_env) {\n return length(symbols) === length(vals)\n ? pair(make_frame(symbols, vals), base_env)\n : error(pair(symbols, vals),\n length(symbols) < length(vals)\n ? \"too many arguments supplied\"\n : \"too few arguments supplied\");\n}\n\nfunction extend_environment(symbols, vals, base_env) {\n return length(symbols) === length(vals)\n ? pair(make_frame(symbols, vals), base_env)\n : length(symbols) < length(vals)\n ? error(\"too many arguments supplied: \" +\n stringify(symbols) + \", \" +\n stringify(vals))\n : error(\"too few arguments supplied: \" +\n stringify(symbols) + \", \" +\n stringify(vals));\n}\n```\n\n```javascript\nextend_environment_example\n enclosing_environment\n make_frame\n\nextend_environment(list(\"x\", \"y\", \"z\"),\n list(1, 2, 3),\n the_empty_environment);\n\ntail(head(extend_environment(list(\"x\", \"y\", \"z\"),\n list(1, 2, 3),\n the_empty_environment)));\n```\n\n```javascript\nThis is used by apply in\n\tsection to bind the\n\tparameters of a function to its arguments.\n```\n\nTo look up a\nsymbol\nin an environment, we scan the list of\nsymbols\nin the first frame.\n\nIf we find the desired\nsymbol,\nwe return the corresponding element in the list of values.\n\nIf we do not\nfind the\nsymbol\nin the current frame, we search the enclosing environment, and so on.", - "token_count": 308, + "content": "the associated values.\n\nTo extend an environment by a new frame that associates\nsymbols\nwith values, we make a frame consisting of the list of\nsymbols\nand the list of values, and we adjoin this to the environment.\n\nWe signal\nan error if the number of\nsymbols\ndoes not match the number of values.\n\n```javascript\nextend_environment\n extend_environment_example\n [ 1, [ 2, [ 3, null ] ] ]\n\nfunction extend_environment(symbols, vals, base_env) {\n return length(symbols) === length(vals)\n ? pair(make_frame(symbols, vals), base_env)\n : error(pair(symbols, vals),\n length(symbols) < length(vals)\n ? \"too many arguments supplied\"\n : \"too few arguments supplied\");\n}\n\nfunction extend_environment(symbols, vals, base_env) {\n return length(symbols) === length(vals)\n ? pair(make_frame(symbols, vals), base_env)\n : length(symbols) < length(vals)\n ? error(\"too many arguments supplied: \" +\n stringify(symbols) + \", \" +\n stringify(vals))\n : error(\"too few arguments supplied: \" +\n stringify(symbols) + \", \" +\n stringify(vals));\n}\n```\n\n```javascript\nextend_environment_example\n enclosing_environment\n make_frame\n\nextend_environment(list(\"x\", \"y\", \"z\"),\n list(1, 2, 3),\n the_empty_environment);\n\ntail(head(extend_environment(list(\"x\", \"y\", \"z\"),\n list(1, 2, 3),\n the_empty_environment)));\n```\n\nThis is used by apply in section to bind the parameters of a function to its arguments.\n\nTo look up a\nsymbol\nin an environment, we scan the list of\nsymbols\nin the first frame.\n\nIf we find the desired\nsymbol,\nwe return the corresponding element in the list of values.\n\nIf we do not\nfind the\nsymbol\nin the current frame, we search the enclosing environment, and so on.\n\nIf we reach the empty environment, we signal an\n\"unbound name\"\nerror.\n\n```javascript\nlookup_variable_value\n lookup_variable_value_example\n 1\n\nfunction lookup_symbol_value(symbol, env) {\n function env_loop(env) {\n function scan(symbols, vals) {\n return is_null(symbols)\n ? env_loop(enclosing_environment(env))\n : symbol === head(symbols)\n ? head(vals)\n : scan(tail(symbols), tail(vals));\n }\n if (env === the_empty_environment) {\n error(symbol, \"unbound name\");\n } else {\n const frame = first_frame(env);\n return scan(frame_symbols(frame), frame_values(frame));\n }\n }\n return env_loop(env);\n}\n```", + "token_count": 296, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2640,8 +3900,8 @@ "chunk_id": "Metalinguistic_Abstraction_Evaluator_Data_Structures_3" }, { - "content": "If we do not\nfind the\nsymbol\nin the current frame, we search the enclosing environment, and so on.\n\nIf we reach the empty environment, we signal an\n\"unbound name\"\nerror.\n\n```javascript\nlookup_variable_value\n lookup_variable_value_example\n 1\n\nfunction lookup_symbol_value(symbol, env) {\n function env_loop(env) {\n function scan(symbols, vals) {\n return is_null(symbols)\n ? env_loop(enclosing_environment(env))\n : symbol === head(symbols)\n ? head(vals)\n : scan(tail(symbols), tail(vals));\n }\n if (env === the_empty_environment) {\n error(symbol, \"unbound name\");\n } else {\n const frame = first_frame(env);\n return scan(frame_symbols(frame), frame_values(frame));\n }\n }\n return env_loop(env);\n}\n```\n\n```javascript\nlookup_variable_value_example\n enclosing_environment\n extend_environment\n make_frame\n\nconst my_environment =\n extend_environment(list(\"x\", \"y\", \"z\"),\n list(1, 2, 3),\n the_empty_environment);\n\nlookup_symbol_value(\"x\", my_environment);\n```\n\n```javascript\nTo assign\n a new value to a symbol in a specified environment, we scan\n for the symbol, just as in\n lookup_symbol_value,\n and change the corresponding value when we find it.\n```\n\n```javascript\nassign_name_value\n assign_name_value_example\n 2\n\nfunction assign_symbol_value(symbol, val, env) {\n function env_loop(env) {\n function scan(symbols, vals) {\n return is_null(symbols)\n ? env_loop(enclosing_environment(env))\n : symbol === head(symbols)\n ? set_head(vals, val)\n : scan(tail(symbols), tail(vals));\n }\n if (env === the_empty_environment) {\n error(symbol, \"unbound name -- assignment\");\n } else {\n const frame = first_frame(env);\n return scan(frame_symbols(frame), frame_values(frame));\n }\n }\n return env_loop(env);\n}\n```\n\n```javascript\nassign_name_value_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_block = parse(\"{ let x = 1; x = 2; }\");\nevaluate(my_block, the_global_environment);\n```\n\nThe method described here is only one of many plausible ways to represent\nenvironments.\n\nSince we used.) In a\nproduction-quality\nJavaScript\nsystem, the speed of the evaluator s environment\noperations especially that of\nsymbol\nlookup has a major\nimpact on the performance of the system.\n\nThe representation described here,\nalthough conceptually simple, is not efficient and would not ordinarily be\nused in a production system.", - "token_count": 277, + "content": "If we reach the empty environment, we signal an\n\"unbound name\"\nerror.\n\nTo assign a new value to a symbol in a specified environment, we scan for the symbol, just as in lookup_symbol_value, and change the corresponding\n\nvalue when we find it.\n\n```javascript\nassign_name_value\n assign_name_value_example\n 2\n\nfunction assign_symbol_value(symbol, val, env) {\n function env_loop(env) {\n function scan(symbols, vals) {\n return is_null(symbols)\n ? env_loop(enclosing_environment(env))\n : symbol === head(symbols)\n ? set_head(vals, val)\n : scan(tail(symbols), tail(vals));\n }\n if (env === the_empty_environment) {\n error(symbol, \"unbound name -- assignment\");\n } else {\n const frame = first_frame(env);\n return scan(frame_symbols(frame), frame_values(frame));\n }\n }\n return env_loop(env);\n}\n```\n\n```javascript\nassign_name_value_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_block = parse(\"{ let x = 1; x = 2; }\");\nevaluate(my_block, the_global_environment);\n```\n\nThe method described here is only one of many plausible ways to represent\nenvironments.\n\nSince we used\ndata abstraction to isolate the rest of the\nevaluator from the detailed choice of representation, we could change the\nenvironment representation if we wanted to.\n\n(See\nexercise.) In a\nproduction-quality\nJavaScript\nsystem, the speed of the evaluator s environment\noperations especially that of\nsymbol\nlookup has a major\nimpact on the performance of the system.\n\nThe representation described here,\nalthough conceptually simple, is not efficient and would not ordinarily be\nused in a production system.", + "token_count": 212, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2650,8 +3910,8 @@ "chunk_id": "Metalinguistic_Abstraction_Evaluator_Data_Structures_4" }, { - "content": "In JavaScript, the scope of a declaration\nis the entire block that immediately surrounds the declaration,\nnot just the portion of the block starting at the point where\nthe declaration occurs.\n\nThis section takes a closer look at this design choice.\n\nLet us revisit the pair of mutually recursive functions is_even and is_odd from Section , declared locally in the body of a function f.\n\n```javascript\nf_is_even_is_odd_2\n\nfunction f(x) {\n function is_even(n) {\n return n === 0\n ? true\n : is_odd(n - 1);\n }\n function is_odd(n) {\n return n === 0\n ? false\n : is_even(n - 1);\n }\n return is_even(x);\n}\n```\n\nOur intention here is that the name\nis_odd\nin the body of the function is_even\nshould refer to the function is_odd\nthat is declared after is_even.\n\nThe scope of the name is_odd is the\nentire body block of is_odd\noccurs.\n\nIndeed, when we consider that\nis_odd is itself defined in terms of\nis_even so that\nis_even and\nis_odd are mutually recursive\nfunctions we see that the only satisfactory interpretation of\nthe two declarations is to regard them as if the names\nis_even and\nis_odd\nwere being added to the environment simultaneously.\n\nMore generally, in\nblock structure, the scope of a local name is the entire block\nin which the declaration is evaluated.\n\nThe evaluation of blocks in the metacircular evaluator of\nsection achieves such\na simultaneous scope for local names by\nis_even and\nis_odd , and any occurrence\nof one of these names refers to the correct binding.\n\nOnce their\ndeclarations are evaluated,\nthese names are bound to their declared values, namely function\nobjects that have the extended environment as their environment\npart.", - "token_count": 275, + "content": "In JavaScript, the scope of a declaration\nis the entire block that immediately surrounds the declaration,\nnot just the portion of the block starting at the point where\nthe declaration occurs.\n\nThis section takes a closer look at this design choice.\n\nLet us revisit the pair of mutually recursive functions is_even and is_odd from Section , declared locally in the body of a function f.\n\n```javascript\nf_is_even_is_odd_2\n\nfunction f(x) {\n function is_even(n) {\n return n === 0\n ? true\n : is_odd(n - 1);\n }\n function is_odd(n) {\n return n === 0\n ? false\n : is_even(n - 1);\n }\n return is_even(x);\n}\n```\n\nOur intention here is that the name\nis_odd\nin the body of the function is_even\nshould refer to the function is_odd\nthat is declared after is_even.\n\nThe scope of the name is_odd is the\nentire body block of , not just the portion of\nthe body of starting at the point where\nthe declaration of is_odd\noccurs.\n\nIndeed, when we consider that\nis_odd is itself defined in terms of\nis_even so that\nis_even and\nis_odd are mutually recursive\nfunctions we see that the only satisfactory interpretation of\nthe two declarations is to regard them as if the names\nis_even and\nis_odd\nwere being added to the environment simultaneously.\n\nMore generally, in\nblock structure, the scope of a local name is the entire block\nin which the declaration is evaluated.\n\nThe evaluation of blocks in the metacircular evaluator of\nsection achieves such\na simultaneous scope for local names by\nscanning out the declarations in the block and extending the current\nenvironment with a frame containing bindings for all the declared\nnames before evaluating the declarations.", + "token_count": 276, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2660,8 +3920,8 @@ "chunk_id": "Metalinguistic_Abstraction_Internal_Declarations_1" }, { - "content": "Once their\ndeclarations are evaluated,\nthese names are bound to their declared values, namely function\nobjects that have the extended environment as their environment\npart.\n\nThus, for example,\nby the time\nis_even\ngets applied in the body of\nf , its environment\nalready contains the correct binding for the symbol\nis_odd , and\nthe evaluation of the name\nis_odd in the body of\nis_even retrieves the correct\nvalue.\n\nimposes a\nruntime burden on the evaluation of blocks: It needs to scan\nthe body of the block for locally declared names, extend the\ncurrent environment with a new frame that binds those names, and evaluate the\nblock body in this extended environment.\n\nAlternatively, the evaluation\nof a block could extend the current environment with an empty frame.\n\nThe evaluation of each declaration in the block body would then add\na new binding to that frame.\n\nTo implement this design, we first simplify\neval_block :\n\n```javascript\neval_block_simplified\n\nfunction eval_block(component, env) {\n const body = block_body(component);\n return evaluate(body, extend_environment(null, null, env);\n}\n```\n\nThe function\neval_declaration can no\nlonger assume that the environment already has a binding for\nthe name.\n\nInstead of using\nassign_symbol_value to\nchange an existing binding, it calls a new function,\nadd_binding_to_frame , to\nadd to the first frame of the environment a binding of the name\nto the value of the value expression.\n\n```javascript\neval_declaration_simplified\n\nfunction eval_declaration(component, env) {\n add_binding_to_frame(\n declaration_symbol(component),\n evaluate(declaration_value_expression(component), env),\n first_frame(env));\n return undefined;\n}\nfunction add_binding_to_frame(symbol, value, frame) {\n set_head(frame, pair(symbol, head(frame)));\n set_tail(frame, pair(value, tail(frame)));\n}\n```\n\nWith sequential declaration processing, the scope of a\ndeclaration is no longer the entire block that immediately surrounds\nthe declaration, but rather just the portion of the block starting at\nthe point where the declaration occurs.", - "token_count": 287, + "content": "The evaluation of blocks in the metacircular evaluator of\nsection achieves such\na simultaneous scope for local names by\nscanning out the declarations in the block and extending the current\nenvironment with a frame containing bindings for all the declared\nnames before evaluating the declarations.\n\nOnce their\ndeclarations are evaluated,\nthese names are bound to their declared values, namely function\nobjects that have the extended environment as their environment\npart.\n\nThus, for example,\nby the time\nis_even\ngets applied in the body of\nf , its environment\nalready contains the correct binding for the symbol\nis_odd , and\nthe evaluation of the name\nis_odd in the body of\nis_even retrieves the correct\nvalue.\n\nThe design of our evaluator of\nsection imposes a\nruntime burden on the evaluation of blocks: It needs to scan\nthe body of the block for locally declared names, extend the\ncurrent environment with a new frame that binds those names, and evaluate the\nblock body in this extended environment.\n\nAlternatively, the evaluation\nof a block could extend the current environment with an empty frame.\n\nThe evaluation of each declaration in the block body would then add\na new binding to that frame.\n\nTo implement this design, we first simplify\neval_block :\n\n```javascript\neval_block_simplified\n\nfunction eval_block(component, env) {\n const body = block_body(component);\n return evaluate(body, extend_environment(null, null, env);\n}\n```\n\nThe function\neval_declaration can no\nlonger assume that the environment already has a binding for\nthe name.\n\nInstead of using\nassign_symbol_value to\nchange an existing binding, it calls a new function,\nadd_binding_to_frame , to\nadd to the first frame of the environment a binding of the name\nto the value of the value expression.\n\n```javascript\neval_declaration_simplified\n\nfunction eval_declaration(component, env) {\n add_binding_to_frame(\n declaration_symbol(component),\n evaluate(declaration_value_expression(component), env),\n first_frame(env));\n return undefined;\n}\nfunction add_binding_to_frame(symbol, value, frame) {\n set_head(frame, pair(symbol, head(frame)));\n set_tail(frame, pair(value, tail(frame)));\n}\n```", + "token_count": 303, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2670,8 +3930,8 @@ "chunk_id": "Metalinguistic_Abstraction_Internal_Declarations_2" }, { - "content": "With sequential declaration processing, the scope of a\ndeclaration is no longer the entire block that immediately surrounds\nthe declaration, but rather just the portion of the block starting at\nthe point where the declaration occurs.\n\nAlthough we no longer have simultaneous scope, sequential\ndeclaration processing\nwill evaluate calls to the function\naccidental reason: Since the declarations\nof the internal functions come first, no calls to these functions\nwill be evaluated until all of them have been declared.\n\nHence,\nis_odd will have been declared by the time\nis_even is executed.\n\nIn fact,\nsequential declaration processing\nwill give the same result as our scanning-out-names evaluator in\nsection\nfor any function\nin which the\nt actually use any of\nthe declared names.\n\nExercise shows\nan example of a function that doesn t\nobey these restrictions, so that the alternative evaluator isn t\nequivalent to our scanning-out-names evaluator.\n\nSequential declaration processing is more efficient and easier to\nimplement than scanning out names.\n\nHowever, with sequential processing, the\ndeclaration to which a name refers may depend on the order in which\nthe statements in a block are evaluated.\n\nIn exercise , we see that\nviews may differ as to whether that is desirable.", - "token_count": 199, + "content": "Instead of using\nassign_symbol_value to\nchange an existing binding, it calls a new function,\nadd_binding_to_frame , to\nadd to the first frame of the environment a binding of the name\nto the value of the value expression.\n\nAlthough we no longer have simultaneous scope, sequential\ndeclaration processing\nwill evaluate calls to the function\nat the beginning of this section\ncorrectly, but for an\naccidental reason: Since the declarations\nof the internal functions come first, no calls to these functions\nwill be evaluated until all of them have been declared.\n\nHence,\nis_odd will have been declared by the time\nis_even is executed.\n\nIn fact,\nsequential declaration processing\nwill give the same result as our scanning-out-names evaluator in\nsection\nfor any function\nin which the\ninternal declarations come first in a body and evaluation of the value\nexpressions for the declared names doesn t actually use any of\nthe declared names.\n\nExercise shows\nan example of a function that doesn t\nobey these restrictions, so that the alternative evaluator isn t\nequivalent to our scanning-out-names evaluator.\n\nSequential declaration processing is more efficient and easier to\nimplement than scanning out names.\n\nHowever, with sequential processing, the\ndeclaration to which a name refers may depend on the order in which\nthe statements in a block are evaluated.\n\nIn exercise , we see that\nviews may differ as to whether that is desirable.", + "token_count": 228, "has_code": false, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2680,8 +3940,8 @@ "chunk_id": "Metalinguistic_Abstraction_Internal_Declarations_3" }, { - "content": "Programmers write programs as text, i.e. sequences of characters, entered\nin a programming environment or a text editor.\n\nTo run our evaluator, we need\nto start with a representation of this program text as a JavaScript value.\n\nIn section we introduced strings to represent\ntext.\n\nWe would like to evaluate programs such as\n\"const size = 2; 5 * size;\"\nfrom section.\n\nUnfortunately, such program text does not provide enough structure to\nthe evaluator.\n\nIn this example, the program parts\n\"size = 2\" and\n\"5 * size\" look similar, but carry\nvery different meanings.\n\nAbstract syntax functions such as\ndeclaration_@value_@expression would be\ndifficult\nand error-prone to implement by examining the program text.\n\nIn this section, we therefore\nintroduce a function\nparse that translates program text\nto a tagged-list representation , reminiscent of\nthe tagged data of section.\n\nFor example, the application of\nparse to the program string\nabove produces a data structure that\nreflects the structure of the program: a sequence consisting\nof a constant declaration associating the name\nsize with the value 2\nand a multiplication.\n\n```javascript\nparse_declaration\n\nparse(\"const size = 2; 5 * size;\");\n```\n\nThe syntax functions used by the evaluator access the tagged-list representation produced by parse.\n\nThe evaluator is reminiscent of the.\n\nBoth programs operate on symbolic\ndata.\n\nIn both programs, the\nresult of operating on\nan object\nis determined by\noperating recursively on the pieces of the\nobject\nand combining\nthe results in a way that depends on the type of the\nobject.\n\nIn both programs we used\nthe objects\nare represented.\n\nIn\nthe differentiation program this meant that the same differentiation\nfunction\ncould deal with algebraic expressions in prefix form, in\ninfix form, or in some other form.\n\nFor the evaluator, this means that", - "token_count": 291, + "content": "Programmers write programs as text, i.e. sequences of characters, entered\nin a programming environment or a text editor.\n\nTo run our evaluator, we need\nto start with a representation of this program text as a JavaScript value.\n\nIn section we introduced strings to represent\ntext.\n\nWe would like to evaluate programs such as\n\"const size = 2; 5 * size;\"\nfrom section.\n\nUnfortunately, such program text does not provide enough structure to\nthe evaluator.\n\nIn this example, the program parts\n\"size = 2\" and\n\"5 * size\" look similar, but carry\nvery different meanings.\n\nAbstract syntax functions such as\ndeclaration_@value_@expression would be\ndifficult\nand error-prone to implement by examining the program text.\n\nIn this section, we therefore\nintroduce a function\nparse that translates program text\nto a tagged-list representation , reminiscent of\nthe tagged data of section.\n\nFor example, the application of\nparse to the program string\nabove produces a data structure that\nreflects the structure of the program: a sequence consisting\nof a constant declaration associating the name\nsize with the value 2\nand a multiplication.\n\n```javascript\nparse_declaration\n\nparse(\"const size = 2; 5 * size;\");\n\nlist(\"sequence\",\n list(list(\"constant_declaration\",\n list(\"name\", \"size\"), list(\"literal\", 2)),\n list(\"binary_operator_combination\", \"*\",\n list(\"literal\", 5), list(\"name\", \"size\"))))\n```\n\nThe syntax functions used by the evaluator access the tagged-list representation produced by parse.\n\nThe evaluator is reminiscent of the\nsymbolic differentiation program\ndiscussed in section.\n\nBoth programs operate on symbolic\ndata.\n\nIn both programs, the\nresult of operating on\nan object\nis determined by\noperating recursively on the pieces of the\nobject\nand combining\nthe results in a way that depends on the type of the\nobject.\n\nIn both programs we used\ndata abstraction to decouple the general rules\nof operation from the details of how\nthe objects\nare represented.", + "token_count": 290, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2690,8 +3950,8 @@ "chunk_id": "Metalinguistic_Abstraction_Representing_Components_1" }, { - "content": "For the evaluator, this means that\n\n```javascript\nthe syntax of the language being evaluated is determined\n\tsolely by parse and\n\tthe functions that classify and extract pieces of the\n\ttagged lists produced by parse.\n```\n\nSyntax abstraction in the evaluator.\n\nFigure depicts the\nparse.\n\nBelow we\ndescribe the parsing of program components and list the\ncorresponding syntax predicates and selectors, as well as\nconstructors if they are needed.\n\n```javascript\nheadline_4_1_2\n\n// functions from SICP JS 4.1.2\n```\n\nLiteral expressions\n\"literal\" and\nthe actual value.\n\\begin{Parsing}\n\\ll\\ \\mathit{literal}\\mhyphen\\mathit{expression}\\ \\gg & = &\n\\texttt{list(\"literal\", }\\mathit{value}\\texttt{)}\n\\end{Parsing}%\nwhere value is\nthe JavaScript value represented by the\nliteral-expression string.\n\nHere $\\ll\\ \\mathit{literal}\\mhyphen\\mathit{expression}\\ \\gg$ denotes the\nresult of parsing the string literal-expression.\n\n```javascript\nparse_literal_example\n\nparse(\"1;\");\n```\n\n```javascript\nparse_literal_example_2\n\nparse(\"'hello world';\");\n```\n\n```javascript\nparse_literal_example_3\n\nparse(\"null;\");\n```\n\nThe syntax predicate for literal expressions is is_literal.\n\n```javascript\nis_literal\n\t tagged_list\n\t is_literal_example\n\t true\n\nfunction is_literal(component) {\n return is_tagged_list(component, \"literal\");\n}\n\nfunction is_literal(component) {\n return is_tagged_list(component, \"literal\");\n}\nfunction literal_value(component) {\n return head(tail(component));\n}\n```\n\n```javascript\nis_literal_example\n\nconst my_program = parse(\"true; 1;\");\nconst my_true_statement = list_ref(list_ref(my_program, 1), 0);\nis_literal(my_true_statement);\n```\n\nIt is defined in terms of the function is_tagged_list , which identifies lists that begin with a designated string:\n\n```javascript\ntagged_list\n\t tagged_list_example\n\t true\n\nfunction is_tagged_list(component, the_tag) {\n return is_pair(component) && head(component) === the_tag;\n}\n```\n\n```javascript\ntagged_list_example\n\nis_tagged_list(list(\"name\", \"x\"), \"name\");\n```\n\nThe second element of the list that results from parsing a literal expression\nis its actual JavaScript value.\n\nThe selector for retrieving the value is\nliteral_value.\n\n```javascript\nliteral_value\n\t literal_value_example\n\t null\n\nfunction literal_value(component) {\n return head(tail(component));\n}\n```\n\n```javascript\nliteral_value_example\n\nliteral_value(parse(\"null;\"));\n```\n\nIn the rest of this section, we just list the syntax predicates and selectors, and omit their declarations if they just access the obvious list\n\nelements.\n\nWe provide a constructor for literals, which will come in handy:\n\n```javascript\nmake_literal\n\nfunction make_literal(value) {\n return list(\"literal\", value);\n}\n```", - "token_count": 308, + "content": "In both programs we used\ndata abstraction to decouple the general rules\nof operation from the details of how\nthe objects\nare represented.\n\nFor the evaluator, this means that\nthe syntax of the language being evaluated is determined solely by parse and the functions that classify and extract pieces of the tagged lists produced by parse.\n\nSyntax abstraction in the evaluator.\n\nFigure depicts the\nabstraction barrier\nformed by the syntax predicates and selectors,\nwhich interface the evaluator to the tagged-list representation of programs,\nwhich in turn is separated from the string representation by\nparse.\n\nBelow we\ndescribe the parsing of program components and list the\ncorresponding syntax predicates and selectors, as well as\nconstructors if they are needed.\n\n```javascript\nheadline_4_1_2\n\n// functions from SICP JS 4.1.2\n```\n\nLiteral expressions\nare parsed into tagged lists with\ntag \"literal\" and\nthe actual value.\n\\begin{Parsing}\n\\ll\\ \\mathit{literal}\\mhyphen\\mathit{expression}\\ \\gg & = &\n\\texttt{list(\"literal\", }\\mathit{value}\\texttt{)}\n\\end{Parsing}%\nwhere value is\nthe JavaScript value represented by the\nliteral-expression string.\n\nHere $\\ll\\ \\mathit{literal}\\mhyphen\\mathit{expression}\\ \\gg$ denotes the\nresult of parsing the string literal-expression.\n\n```javascript\nparse_literal_example\n\nparse(\"1;\");\n\nlist(\"literal\", 1)\n```\n\n```javascript\nparse_literal_example_2\n\nparse(\"'hello world';\");\n\nlist(\"literal\", \"hello world\")\n```\n\n```javascript\nparse_literal_example_3\n\nparse(\"null;\");\n\nlist(\"literal\", null)\n```\n\nThe syntax predicate for literal expressions is is_literal.\n\n```javascript\nis_literal\n\t tagged_list\n\t is_literal_example\n\t true\n\nfunction is_literal(component) {\n return is_tagged_list(component, \"literal\");\n}\n\nfunction is_literal(component) {\n return is_tagged_list(component, \"literal\");\n}\nfunction literal_value(component) {\n return head(tail(component));\n}\n```\n\n```javascript\nis_literal_example\n\nconst my_program = parse(\"true; 1;\");\nconst my_true_statement = list_ref(list_ref(my_program, 1), 0);\nis_literal(my_true_statement);\n```\n\nIt is defined in terms of the function is_tagged_list , which identifies lists that begin with a designated string:\n\n```javascript\ntagged_list\n\t tagged_list_example\n\t true\n\nfunction is_tagged_list(component, the_tag) {\n return is_pair(component) && head(component) === the_tag;\n}\n```\n\n```javascript\ntagged_list_example\n\nis_tagged_list(list(\"name\", \"x\"), \"name\");\n```\n\nThe second element of the list that results from parsing a literal expression\nis its actual JavaScript value.\n\nThe selector for retrieving the value is\nliteral_value.", + "token_count": 310, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2700,8 +3960,8 @@ "chunk_id": "Metalinguistic_Abstraction_Representing_Components_2" }, { - "content": "We provide a constructor for literals, which will come in handy:\n\nThe tagged-list representation for\n\"name\" as first\nelement and the string representing the name as second element.\n\\begin{Parsing}\n\\ll\\ \\mathit{name}\\ \\gg & = &\n\\texttt{list(\"name\", }\\mathit{symbol}\\texttt{)}\n\\end{Parsing}%\nwhere symbol is a string\nthat contains the characters that make up the\nname as written in the program.\n\nThe syntax predicate for names is\nis_name.\n\n```javascript\nvariable\n\t variable_example\n\t tagged_list\n\nfunction is_name(component) {\n return is_tagged_list(component, \"name\");\n}\n```\n\nThe symbol is accessed using the selector symbol_of_name.\n\n```javascript\nsymbol_of_name\n\t variable_example\n\t tagged_list\n\t variable\n\nfunction symbol_of_name(component) {\n return head(tail(component));\n}\n```\n\n```javascript\nvariable_example\n\nconst my_name_statement = parse(\"x;\");\ndisplay(is_name(my_name_statement));\ndisplay(symbol_of_name(my_name_statement));\n```\n\nWe provide a constructor for names, to be used by operator_combination_to_application :\n\n```javascript\nmake_name\n\t variable_example\n\t tagged_list\n\nfunction make_name(symbol) {\n return list(\"name\", symbol);\n}\n```\n\nWe do not need to distinguish between expressions and parse can ignore the difference between the two kinds of components: \\begin{ParsingNoPostPadding} \\ll\\ \\mathit{expression}\\texttt{;}\\ \\gg &\n\n= & \\ll\\ \\mathit{expression}\\ \\gg \\end{ParsingNoPostPadding}\n\nFunction applications\n\n```javascript\n$\\ll\\ $fun-expr(arg-expr$_1$, $\\ldots$, arg-expr$_n$)$\\ \\gg$ =\n list(\"application\",\n $\\ll\\ $fun-expr$\\ \\gg$,\n list($\\ll\\ $arg-expr$_1\\;\\gg$, $\\ldots$, $\\ll\\ $arg-expr$_n\\;\\gg$))\n```\n\nWe declare is_application as the syntax predicate and function_expression and arg_expressions as the selectors.\n\n```javascript\napplication\n tagged_list\n application_example\n\nfunction is_application(component) {\n return is_tagged_list(component, \"application\");\n}\nfunction function_expression(component) {\n return head(tail(component));\n}\nfunction arg_expressions(component) {\n return head(tail(tail(component)));\n}\n```\n\n```javascript\napplication_example\n\nconst my_application = parse(\"math_pow(3, 4);\");\ndisplay(is_application(my_application));\ndisplay(function_expression(my_application));\nconst my_expressions = arg_expressions(my_application);\ndisplay(no_arg_expressions(my_expressions));\ndisplay(first_arg_expression(my_expressions));\ndisplay(rest_arg_expressions(my_expressions));\n```\n\nWe add a constructor for function applications, to be used by operator_combination_to_application :\n\n```javascript\nmake_application\n variable\n variable_example\n tagged_list\n\nfunction make_application(function_expression, argument_expressions) {\n return list(\"application\",\n function_expression, argument_expressions);\n}\n```\n\nConditional expressions\n\n```javascript\n$\\ll\\ $predicate ? consequent-expression : alternative-expression$\\ \\gg$ =\n list(\"conditional_expression\",\n $\\ll\\ $predicate$\\ \\gg$,\n $\\ll\\ $consequent-expression$\\ \\gg$,\n $\\ll\\ $alternative-expression$\\ \\gg$)\n```\n\nSimilarly, conditional statements are parsed as follows:\n\n```javascript\n$\\ll\\ $if (predicate) consequent-block else alternative-block$\\ \\gg$ =\n list(\"conditional_statement\",\n $\\ll\\ $predicate$\\ \\gg$,\n $\\ll\\ $consequent-block$\\ \\gg$,\n $\\ll\\ $alternative-block$\\ \\gg$)\n```", - "token_count": 311, + "content": "The selector for retrieving the value is\nliteral_value.\n\n```javascript\nliteral_value_example\n\nliteral_value(parse(\"null;\"));\n\nnull\n```\n\nIn the rest of this section, we just list the syntax predicates and selectors, and omit their declarations if they just access the obvious list\n\nelements.\n\nWe provide a constructor for literals, which will come in handy:\n\n```javascript\nmake_literal\n\nfunction make_literal(value) {\n return list(\"literal\", value);\n}\n```\n\nThe tagged-list representation for\nnames includes the tag \"name\" as first\nelement and the string representing the name as second element.\n\\begin{Parsing}\n\\ll\\ \\mathit{name}\\ \\gg & = &\n\\texttt{list(\"name\", }\\mathit{symbol}\\texttt{)}\n\\end{Parsing}%\nwhere symbol is a string\nthat contains the characters that make up the\nname as written in the program.\n\nThe syntax predicate for names is\nis_name.\n\n```javascript\nvariable\n\t variable_example\n\t tagged_list\n\nfunction is_name(component) {\n return is_tagged_list(component, \"name\");\n}\n```\n\nThe symbol is accessed using the selector symbol_of_name.\n\n```javascript\nsymbol_of_name\n\t variable_example\n\t tagged_list\n\t variable\n\nfunction symbol_of_name(component) {\n return head(tail(component));\n}\n```\n\n```javascript\nvariable_example\n\nconst my_name_statement = parse(\"x;\");\ndisplay(is_name(my_name_statement));\ndisplay(symbol_of_name(my_name_statement));\n```\n\nWe provide a constructor for names, to be used by operator_combination_to_application :\n\n```javascript\nmake_name\n\t variable_example\n\t tagged_list\n\nfunction make_name(symbol) {\n return list(\"name\", symbol);\n}\n```\n\nWe do not need to distinguish between expressions and\nStatements.\n\nConsequently,\nparse can ignore the difference\nbetween the two kinds of components:\n\\begin{ParsingNoPostPadding}\n\\ll\\ \\mathit{expression}\\texttt{;}\\ \\gg & = &\n\\ll\\ \\mathit{expression}\\ \\gg\n\\end{ParsingNoPostPadding}\n\nFunction applications are parsed as follows:\n\n```javascript\n$\\ll\\ $fun-expr(arg-expr$_1$, $\\ldots$, arg-expr$_n$)$\\ \\gg$ =\n list(\"application\",\n $\\ll\\ $fun-expr$\\ \\gg$,\n list($\\ll\\ $arg-expr$_1\\;\\gg$, $\\ldots$, $\\ll\\ $arg-expr$_n\\;\\gg$))\n```\n\nWe declare is_application as the syntax predicate and function_expression and arg_expressions as the selectors.\n\n```javascript\napplication\n tagged_list\n application_example\n\nfunction is_application(component) {\n return is_tagged_list(component, \"application\");\n}\nfunction function_expression(component) {\n return head(tail(component));\n}\nfunction arg_expressions(component) {\n return head(tail(tail(component)));\n}\n```\n\n```javascript\napplication_example\n\nconst my_application = parse(\"math_pow(3, 4);\");\ndisplay(is_application(my_application));\ndisplay(function_expression(my_application));\nconst my_expressions = arg_expressions(my_application);\ndisplay(no_arg_expressions(my_expressions));\ndisplay(first_arg_expression(my_expressions));\ndisplay(rest_arg_expressions(my_expressions));\n```\n\nWe add a constructor for function applications, to be used by operator_combination_to_application :", + "token_count": 306, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2710,8 +3970,8 @@ "chunk_id": "Metalinguistic_Abstraction_Representing_Components_3" }, { - "content": "Similarly, conditional statements are parsed as follows:\n\nThe syntax predicate is_conditional returns true for both kinds of conditionals, and the selectors conditional_predicate , conditional_consequent , and conditional_alternative can be applied to both\n\nkinds.\n\n```javascript\nif\n\t tagged_list\n\t if_example\n\t [ 'literal', [ 2, null ] ]\n\nfunction is_conditional(component) {\n return is_tagged_list(component, \"conditional_expression\") ||\n is_tagged_list(component, \"conditional_statement\");\n}\nfunction conditional_predicate(component) {\n return list_ref(component, 1);\n}\nfunction conditional_consequent(component) {\n return list_ref(component, 2);\n}\nfunction conditional_alternative(component) {\n return list_ref(component, 3);\n}\n```\n\n```javascript\nif_example\n\nconst my_cond_expr =\n parse(\"true ? 1 : 2;\");\ndisplay(is_conditional(my_cond_expr));\ndisplay(conditional_predicate(my_cond_expr));\ndisplay(conditional_consequent(my_cond_expr));\ndisplay(conditional_alternative(my_cond_expr));\n\nconst my_cond_expr =\n parse(\"true ? 1 : 2;\");\nis_conditional(my_cond_expr);\nconditional_predicate(my_cond_expr);\nconditional_consequent(my_cond_expr);\nconditional_alternative(my_cond_expr);\n```\n\nA lambda expression\n\n```javascript\n$\\ll\\ $(name$_1$, $\\ldots$, name$_n$) => expression$\\ \\gg$ =\n $\\ll\\ $(name$_1$, $\\ldots$, name$_n$) => { return expression; }$\\ \\gg$\n```\n\nA lambda expression whose body is a block is parsed as follows:\n\n```javascript\n$\\ll\\ $(name$_1$, $\\ldots$, name$_n$) => block$\\ \\gg$ =\n list(\"lambda_expression\",\n list($\\ll\\ $name$_1\\;\\gg$, $\\ldots$, $\\ll\\ $name$_n\\;\\gg$),\n $\\ll\\ $block$\\ \\gg$)\n```\n\nThe syntax predicate is\nis_lambda_expression\nand the selector for the body of the lambda expression is\nlambda_body.\n\nThe selector for the parameters, called\nlambda_parameter_symbols ,\nadditionally extracts the symbols from the names.\n\n```javascript\nlambda\n\t variable\n\t symbol_of_name\n\t tagged_list\n\t lambda_example\n\nfunction lambda_parameter_symbols(component) {\n return map(symbol_of_name, head(tail(component)));\n}\n\nfunction is_lambda_expression(component) {\n return is_tagged_list(component, \"lambda_expression\");\n}\nfunction lambda_parameter_symbols(component) {\n return map(symbol_of_name, head(tail(component)));\n}\nfunction lambda_body(component) {\n return head(tail(tail(component)));\n}\n```\n\n```javascript\nlambda_example\n\nconst my_lambda = parse(\"x => x\");\ndisplay(is_lambda_expression(my_lambda));\ndisplay(lambda_parameter_symbols(my_lambda));\ndisplay(lambda_body(my_lambda));\n```\n\nThe function function_decl_to_constant_decl needs a constructor for lambda expressions:\n\n```javascript\nmake_lambda_expression\n\t variable_example\n\t tagged_list\n\t variable\n\nfunction make_lambda_expression(parameters, body) {\n return list(\"lambda_expression\", parameters, body);\n}\n```\n\nA sequence statement sequence of statements is parsed as follows:\n\n```javascript\n$\\ll\\ $statement$_1$ $\\cdots$ statement$_n\\;\\gg$ =\n list(\"sequence\", list($\\ll\\ $statement$_1\\;\\gg$, $\\ldots$, $\\ll\\ $statement$_n\\;\\gg$))\n```\n\nThe syntax predicate is\nis_sequence and\nthe selector is sequence_statements.\n\nWe retrieve the first of a list of statements using\nfirst_statement and\nthe remaining statements using\nrest_statements.", - "token_count": 309, + "content": "We add a constructor for function applications, to be used by operator_combination_to_application :\n\nConditional expressions are parsed as follows:\n\n```javascript\n$\\ll\\ $predicate ? consequent-expression : alternative-expression$\\ \\gg$ =\n list(\"conditional_expression\",\n $\\ll\\ $predicate$\\ \\gg$,\n $\\ll\\ $consequent-expression$\\ \\gg$,\n $\\ll\\ $alternative-expression$\\ \\gg$)\n```\n\nSimilarly, conditional statements are parsed as follows:\n\n```javascript\n$\\ll\\ $if (predicate) consequent-block else alternative-block$\\ \\gg$ =\n list(\"conditional_statement\",\n $\\ll\\ $predicate$\\ \\gg$,\n $\\ll\\ $consequent-block$\\ \\gg$,\n $\\ll\\ $alternative-block$\\ \\gg$)\n```\n\nThe syntax predicate is_conditional returns true for both kinds of conditionals, and the selectors conditional_predicate , conditional_consequent , and conditional_alternative can be applied to both\n\nkinds.\n\n```javascript\nif\n\t tagged_list\n\t if_example\n\t [ 'literal', [ 2, null ] ]\n\nfunction is_conditional(component) {\n return is_tagged_list(component, \"conditional_expression\") ||\n is_tagged_list(component, \"conditional_statement\");\n}\nfunction conditional_predicate(component) {\n return list_ref(component, 1);\n}\nfunction conditional_consequent(component) {\n return list_ref(component, 2);\n}\nfunction conditional_alternative(component) {\n return list_ref(component, 3);\n}\n```\n\n```javascript\nif_example\n\nconst my_cond_expr =\n parse(\"true ? 1 : 2;\");\ndisplay(is_conditional(my_cond_expr));\ndisplay(conditional_predicate(my_cond_expr));\ndisplay(conditional_consequent(my_cond_expr));\ndisplay(conditional_alternative(my_cond_expr));\n\nconst my_cond_expr =\n parse(\"true ? 1 : 2;\");\nis_conditional(my_cond_expr);\nconditional_predicate(my_cond_expr);\nconditional_consequent(my_cond_expr);\nconditional_alternative(my_cond_expr);\n```\n\nA lambda expression whose body is an expression is parsed as if the body consisted of a block containing a single return statement whose return\n\nexpression is the body of the lambda expression.\n\n```javascript\n$\\ll\\ $(name$_1$, $\\ldots$, name$_n$) => expression$\\ \\gg$ =\n $\\ll\\ $(name$_1$, $\\ldots$, name$_n$) => { return expression; }$\\ \\gg$\n```\n\nA lambda expression whose body is a block is parsed as follows:\n\n```javascript\n$\\ll\\ $(name$_1$, $\\ldots$, name$_n$) => block$\\ \\gg$ =\n list(\"lambda_expression\",\n list($\\ll\\ $name$_1\\;\\gg$, $\\ldots$, $\\ll\\ $name$_n\\;\\gg$),\n $\\ll\\ $block$\\ \\gg$)\n```\n\nThe syntax predicate is\nis_lambda_expression\nand the selector for the body of the lambda expression is\nlambda_body.\n\nThe selector for the parameters, called\nlambda_parameter_symbols ,\nadditionally extracts the symbols from the names.\n\n```javascript\nlambda\n\t variable\n\t symbol_of_name\n\t tagged_list\n\t lambda_example\n\nfunction lambda_parameter_symbols(component) {\n return map(symbol_of_name, head(tail(component)));\n}\n\nfunction is_lambda_expression(component) {\n return is_tagged_list(component, \"lambda_expression\");\n}\nfunction lambda_parameter_symbols(component) {\n return map(symbol_of_name, head(tail(component)));\n}\nfunction lambda_body(component) {\n return head(tail(tail(component)));\n}\n```", + "token_count": 312, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2720,8 +3980,8 @@ "chunk_id": "Metalinguistic_Abstraction_Representing_Components_4" }, { - "content": "We retrieve the first of a list of statements using\nfirst_statement and\nthe remaining statements using\nrest_statements.\n\nWe test\nwhether the list is empty using the predicate\nis_empty_sequence and\nwhether it contains only one element\nusing the predicate\nis_last_statement.\n\n```javascript\nbegin\n tagged_list\n begin_example\n [ 'literal', [ 45, null ] ]\n\nfunction first_statement(stmts) { return head(stmts); }\n\nfunction rest_statements(stmts) { return tail(stmts); }\n\nfunction is_empty_sequence(stmts) { return is_null(stmts); }\n\nfunction is_last_statement(stmts) { return is_null(tail(stmts)); }\n\nfunction is_sequence(stmt) {\n return is_tagged_list(stmt, \"sequence\");\n}\nfunction sequence_statements(stmt) {\n return head(tail(stmt));\n}\nfunction first_statement(stmts) {\n return head(stmts);\n}\nfunction rest_statements(stmts) {\n return tail(stmts);\n}\nfunction is_empty_sequence(stmts) {\n return is_null(stmts);\n}\nfunction is_last_statement(stmts) {\n return is_null(tail(stmts));\n}\n```\n\n```javascript\nbegin_example\n\nconst my_sequence = parse(\"1; true; 45;\");\ndisplay(is_sequence(my_sequence));\nconst my_actions = sequence_statements(my_sequence);\ndisplay(is_empty_sequence(my_actions));\ndisplay(is_last_statement(my_actions));\ndisplay(first_statement(my_actions));\ndisplay(rest_statements(my_actions));\n\nconst my_sequence = parse(\"1; true; 45;\");\nis_sequence(my_sequence);\nconst my_actions = sequence_statements(my_sequence);\nis_empty_sequence(my_actions);\nis_last_statement(my_actions);\nfirst_statement(my_actions);\nrest_statements(my_actions);\nlist_ref(rest_statements(my_actions), 1);\n```\n\nBlocks\n\\begin{Parsing}\n\\ll\\ \\texttt{\\{}\\ \\mathit{statements}\\ \\texttt{\\}}\\ \\gg\n& = &\n\\texttt{list(\"block\",}\\ \\ll\\ \\mathit{statements}\\ \\gg \\texttt{)}\n\\end{Parsing}%\nHere statements refers to a sequence of\nstatements, as shown above.\n\nThe syntax predicate is\nis_block\nand the selector is\nblock_body.\n\n```javascript\nblock\n\t tagged_list\n\t block_example\n\nfunction is_block(component) {\n return is_tagged_list(component, \"block\");\n}\nfunction block_body(component) {\n return head(tail(component));\n}\n\nfunction is_block(component) {\n return is_tagged_list(component, \"block\");\n}\nfunction block_body(component) {\n return head(tail(component));\n}\nfunction make_block(statement) {\n return list(\"block\", statement);\n}\n```\n\n```javascript\nblock_example\n\nconst my_block = parse(\"{ 1; true; 45; }\");\ndisplay(is_block(my_block));\ndisplay(block_body(my_block));\n```\n\nReturn statements \\begin{Parsing} \\ll\\ \\textbf{\\texttt{return}}\\ \\mathit{expression} \\texttt{;}\\ \\gg & = & \\texttt{list(\"return_statement\",}\\ \\ll\\ \\mathit{expression}\\ \\gg \\texttt{)} \\end{Parsing}% The syntax predicate and selector are, respectively, is_return_statement\n\nand return_expression.\n\n```javascript\nreturn\n\t tagged_list\n\t return_example\n\t [ 'name', [ 'x', null ] ]\n\nfunction is_return_statement(component) {\n return is_tagged_list(component, \"return_statement\");\n}\nfunction return_expression(component) {\n return head(tail(component));\n}\n```\n\n```javascript\nreturn_example\n\nconst my_function_declaration = parse(\"function f(x) { return x; }\");\nconst my_return = list_ref(my_function_declaration, 3);\nlist_ref(my_return, 1);\n```", - "token_count": 306, + "content": "The selector for the parameters, called\nlambda_parameter_symbols ,\nadditionally extracts the symbols from the names.\n\nThe function function_decl_to_constant_decl needs a constructor for lambda expressions:\n\n```javascript\nmake_lambda_expression\n\t variable_example\n\t tagged_list\n\t variable\n\nfunction make_lambda_expression(parameters, body) {\n return list(\"lambda_expression\", parameters, body);\n}\n```\n\nA sequence statement\npackages a sequence of statements into a\nsingle statement.\n\nA sequence of statements is parsed as follows:\n\n```javascript\n$\\ll\\ $statement$_1$ $\\cdots$ statement$_n\\;\\gg$ =\n list(\"sequence\", list($\\ll\\ $statement$_1\\;\\gg$, $\\ldots$, $\\ll\\ $statement$_n\\;\\gg$))\n```\n\nThe syntax predicate is\nis_sequence and\nthe selector is sequence_statements.\n\nWe retrieve the first of a list of statements using\nfirst_statement and\nthe remaining statements using\nrest_statements.\n\nWe test\nwhether the list is empty using the predicate\nis_empty_sequence and\nwhether it contains only one element\nusing the predicate\nis_last_statement.\n\n```javascript\nbegin\n tagged_list\n begin_example\n [ 'literal', [ 45, null ] ]\n\nfunction first_statement(stmts) { return head(stmts); }\n\nfunction rest_statements(stmts) { return tail(stmts); }\n\nfunction is_empty_sequence(stmts) { return is_null(stmts); }\n\nfunction is_last_statement(stmts) { return is_null(tail(stmts)); }\n\nfunction is_sequence(stmt) {\n return is_tagged_list(stmt, \"sequence\");\n}\nfunction sequence_statements(stmt) {\n return head(tail(stmt));\n}\nfunction first_statement(stmts) {\n return head(stmts);\n}\nfunction rest_statements(stmts) {\n return tail(stmts);\n}\nfunction is_empty_sequence(stmts) {\n return is_null(stmts);\n}\nfunction is_last_statement(stmts) {\n return is_null(tail(stmts));\n}\n```\n\n```javascript\nbegin_example\n\nconst my_sequence = parse(\"1; true; 45;\");\ndisplay(is_sequence(my_sequence));\nconst my_actions = sequence_statements(my_sequence);\ndisplay(is_empty_sequence(my_actions));\ndisplay(is_last_statement(my_actions));\ndisplay(first_statement(my_actions));\ndisplay(rest_statements(my_actions));\n\nconst my_sequence = parse(\"1; true; 45;\");\nis_sequence(my_sequence);\nconst my_actions = sequence_statements(my_sequence);\nis_empty_sequence(my_actions);\nis_last_statement(my_actions);\nfirst_statement(my_actions);\nrest_statements(my_actions);\nlist_ref(rest_statements(my_actions), 1);\n```\n\nBlocks\nare parsed as follows:\n\\begin{Parsing}\n\\ll\\ \\texttt{\\{}\\ \\mathit{statements}\\ \\texttt{\\}}\\ \\gg\n& = &\n\\texttt{list(\"block\",}\\ \\ll\\ \\mathit{statements}\\ \\gg \\texttt{)}\n\\end{Parsing}%\nHere statements refers to a sequence of\nstatements, as shown above.\n\nThe syntax predicate is\nis_block\nand the selector is\nblock_body.\n\n```javascript\nblock\n\t tagged_list\n\t block_example\n\nfunction is_block(component) {\n return is_tagged_list(component, \"block\");\n}\nfunction block_body(component) {\n return head(tail(component));\n}\n\nfunction is_block(component) {\n return is_tagged_list(component, \"block\");\n}\nfunction block_body(component) {\n return head(tail(component));\n}\nfunction make_block(statement) {\n return list(\"block\", statement);\n}\n```", + "token_count": 308, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2730,8 +3990,8 @@ "chunk_id": "Metalinguistic_Abstraction_Representing_Components_5" }, { - "content": "and return_expression.\n\nAssignments\n\\begin{Parsing}\n\\ll\\;\\mathit{name} \\ \\texttt{=}\\ \\mathit{expression}\\;\\gg & = &\n\\texttt{list(\"assignment\",}\\ \\ll\\;\\mathit{name}\\;\\gg \\texttt{, }\\ll\\;\\mathit{expression}\\;\\gg \\texttt{)}\n\\end{Parsing}%\nThe syntax predicate is\nis_assignment\nand the selectors are\nassignment_symbol\nand\nassignment_value_expression.\n\nThe symbol is wrapped in a tagged list representing the name, and thus\nassignment_symbol needs to\nunwrap it.\n\n```javascript\nassignment\n tagged_list\n assignment_example\n [ 'literal', [ 1, null ] ]\n\nfunction assignment_symbol(component) {\n return symbol_of_name(head(tail(component))));\n}\n\nfunction is_assignment(component) {\n return is_tagged_list(component, \"assignment\");\n}\nfunction assignment_symbol(component) {\n return head(tail(head(tail(component))));\n}\nfunction assignment_value_expression(component) {\n return head(tail(tail(component)));\n}\n```\n\n```javascript\nassignment_example\n\nconst my_assignment_statement = parse(\"x = 1;\");\ndisplay(assignment_symbol(my_assignment_statement));\ndisplay(assignment_value_expression(my_assignment_statement));\n\nconst my_assignment_statement = parse(\"x = 1;\");\nassignment_symbol(my_assignment_statement);\nassignment_value_expression(my_assignment_statement);\n```\n\nConstant and variable declarations\n\n```javascript\n$\\ll\\ $const name = expression;$\\ \\gg$ =\n list(\"constant_declaration\", $\\ll\\ $name$\\ \\gg$, $\\ll\\ $expression$\\ \\gg$)\n\n$\\ll\\ $let name = expression;$\\ \\gg$ =\n list(\"variable_declaration\", $\\ll\\ $name$\\ \\gg$, $\\ll\\ $expression$\\ \\gg$)\n```\n\nThe selectors declaration_symbol and declaration_value_expression apply to both kinds.\n\n```javascript\ndeclaration_symbol\n\t tagged_list\n\t definition_example\n\nfunction declaration_symbol(component) {\n return symbol_of_name(head(tail(component)));\n}\nfunction declaration_value_expression(component) {\n return head(tail(tail(component)));\n}\n```\n\nThe function function_decl_to_constant_decl needs a constructor for constant declarations:\n\n```javascript\nmake_constant_declaration\n\t tagged_list\n\t definition\n\t definition_example\n\nfunction make_constant_declaration(name, value_expression) {\n return list(\"constant_declaration\", name, value_expression);\n}\n```\n\nFunction declarations\n\n```javascript\n$\\ll\\ $function name(name$_1$, $\\ldots$ name$_n$) block$\\ \\gg$ =\n list(\"function_declaration\",\n $\\ll\\ $name$\\ \\gg$,\n list($\\ll\\ $name$_1\\;\\gg$, $\\ldots$, $\\ll\\ $name$_n\\;\\gg$),\n $\\ll\\ $block$\\ \\gg$)\n```\n\nThe syntax predicate\nis_function_declaration\nrecognizes these.\n\nThe selectors are\nfunction_declaration_name ,\nfunction_declaration_parameters , and\nfunction_declaration_body.\n\n```javascript\nfunction_declaration_syntax\n tagged_list\n\nfunction is_function_declaration(component) {\n return is_tagged_list(component, \"function_declaration\");\n}\nfunction function_declaration_name(component) {\n return list_ref(component, 1);\n}\nfunction function_declaration_parameters(component) {\n return list_ref(component, 2);\n}\nfunction function_declaration_body(component) {\n return list_ref(component, 3);\n}\n```\n\nThe syntax predicate is_declaration returns true for all three kinds of declarations.\n\n```javascript\ndefinition\n\t tagged_list\n\t definition_example\n\nfunction is_declaration(component) {\n return is_tagged_list(component, \"constant_declaration\") ||\n is_tagged_list(component, \"variable_declaration\") ||\n is_tagged_list(component, \"function_declaration\");\n}\n```\n\n```javascript\ndefinition_example\n\nconst my_declaration_statement = parse(\"let x = 1;\");\ndisplay(is_declaration(my_declaration_statement));\ndisplay(declaration_symbol(my_declaration_statement));\ndisplay(declaration_value_expression(my_declaration_statement));\n```\n\nSome evaluate transforms into a constant declaration whose value expression is a lambda expression.", - "token_count": 315, + "content": "The syntax predicate is\nis_block\nand the selector is\nblock_body.\n\nReturn statements are parsed as follows: \\begin{Parsing} \\ll\\ \\textbf{\\texttt{return}}\\ \\mathit{expression} \\texttt{;}\\ \\gg & = & \\texttt{list(\"return_statement\",}\\ \\ll\\ \\mathit{expression}\\ \\gg \\texttt{)} \\end{Parsing}% The syntax predicate and\n\nselector are, respectively, is_return_statement and return_expression.\n\n```javascript\nreturn\n\t tagged_list\n\t return_example\n\t [ 'name', [ 'x', null ] ]\n\nfunction is_return_statement(component) {\n return is_tagged_list(component, \"return_statement\");\n}\nfunction return_expression(component) {\n return head(tail(component));\n}\n```\n\n```javascript\nreturn_example\n\nconst my_function_declaration = parse(\"function f(x) { return x; }\");\nconst my_return = list_ref(my_function_declaration, 3);\nlist_ref(my_return, 1);\n```\n\nAssignments\nare parsed as follows:\n\\begin{Parsing}\n\\ll\\;\\mathit{name} \\ \\texttt{=}\\ \\mathit{expression}\\;\\gg & = &\n\\texttt{list(\"assignment\",}\\ \\ll\\;\\mathit{name}\\;\\gg \\texttt{, }\\ll\\;\\mathit{expression}\\;\\gg \\texttt{)}\n\\end{Parsing}%\nThe syntax predicate is\nis_assignment\nand the selectors are\nassignment_symbol\nand\nassignment_value_expression.\n\nThe symbol is wrapped in a tagged list representing the name, and thus\nassignment_symbol needs to\nunwrap it.\n\n```javascript\nassignment\n tagged_list\n assignment_example\n [ 'literal', [ 1, null ] ]\n\nfunction assignment_symbol(component) {\n return symbol_of_name(head(tail(component))));\n}\n\nfunction is_assignment(component) {\n return is_tagged_list(component, \"assignment\");\n}\nfunction assignment_symbol(component) {\n return head(tail(head(tail(component))));\n}\nfunction assignment_value_expression(component) {\n return head(tail(tail(component)));\n}\n```\n\n```javascript\nassignment_example\n\nconst my_assignment_statement = parse(\"x = 1;\");\ndisplay(assignment_symbol(my_assignment_statement));\ndisplay(assignment_value_expression(my_assignment_statement));\n\nconst my_assignment_statement = parse(\"x = 1;\");\nassignment_symbol(my_assignment_statement);\nassignment_value_expression(my_assignment_statement);\n```\n\nConstant and variable declarations are parsed as follows:\n\n```javascript\n$\\ll\\ $const name = expression;$\\ \\gg$ =\n list(\"constant_declaration\", $\\ll\\ $name$\\ \\gg$, $\\ll\\ $expression$\\ \\gg$)\n\n$\\ll\\ $let name = expression;$\\ \\gg$ =\n list(\"variable_declaration\", $\\ll\\ $name$\\ \\gg$, $\\ll\\ $expression$\\ \\gg$)\n```\n\nThe selectors declaration_symbol and declaration_value_expression apply to both kinds.\n\n```javascript\ndeclaration_symbol\n\t tagged_list\n\t definition_example\n\nfunction declaration_symbol(component) {\n return symbol_of_name(head(tail(component)));\n}\nfunction declaration_value_expression(component) {\n return head(tail(tail(component)));\n}\n```\n\nThe function function_decl_to_constant_decl needs a constructor for constant declarations:\n\n```javascript\nmake_constant_declaration\n\t tagged_list\n\t definition\n\t definition_example\n\nfunction make_constant_declaration(name, value_expression) {\n return list(\"constant_declaration\", name, value_expression);\n}\n```\n\nFunction declarations are parsed as follows:\n\n```javascript\n$\\ll\\ $function name(name$_1$, $\\ldots$ name$_n$) block$\\ \\gg$ =\n list(\"function_declaration\",\n $\\ll\\ $name$\\ \\gg$,\n list($\\ll\\ $name$_1\\;\\gg$, $\\ldots$, $\\ll\\ $name$_n\\;\\gg$),\n $\\ll\\ $block$\\ \\gg$)\n```\n\nThe syntax predicate\nis_function_declaration\nrecognizes these.", + "token_count": 312, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2740,8 +4000,8 @@ "chunk_id": "Metalinguistic_Abstraction_Representing_Components_6" }, { - "content": "Some evaluate transforms into a constant declaration whose value expression is a lambda expression.\n\n```javascript\nfunction_declaration\n\t tagged_list\n\nfunction function_decl_to_constant_decl(component) {\n return make_constant_declaration(\n function_declaration_name(component),\n make_lambda_expression(\n function_declaration_parameters(component),\n function_declaration_body(component)));\n}\n\nfunction is_function_declaration(component) {\n return is_tagged_list(component, \"function_declaration\");\n}\nfunction function_declaration_name(component) {\n return list_ref(component, 1);\n}\nfunction function_declaration_parameters(component) {\n return list_ref(component, 2);\n}\nfunction function_declaration_body(component) {\n return list_ref(component, 3);\n}\nfunction function_decl_to_constant_decl(component) {\n return make_constant_declaration(\n function_declaration_name(component),\n make_lambda_expression(\n function_declaration_parameters(component),\n function_declaration_body(component)));\n}\n```\n\nImplementing the evaluation of function declarations in this way simplifies the evaluator because it reduces the number of syntactic forms for which the evaluation process\n\nmust be explicitly specified.\n\nSimilarly, we define\n\n```javascript\n$\\ll\\ $unary-operator expression$\\ \\gg$ =\n list(\"unary_operator_combination\",\n \"unary-operator\",\n list($\\ll\\ $expression$\\ \\gg$))\n```\n\nwhere unary-operator is\n!\n\n(for logical negation) or\n-unary (for numeric negation), and\n\n```javascript\n$\\ll\\ $expression$_1$ binary-operator expression$_2\\;\\gg$ =\n list(\"binary_operator_combination\",\n \"binary-operator\",\n list($\\ll\\ $expression$_1\\;\\gg$, $\\ll\\ $expression$_2\\;\\gg$))\n```\n\nwhere binary-operator is\n+ ,\n- ,\n* ,\n/ ,\n% ,\n=== ,\n!== ,\n> ,\n< ,\n>= or\n<=.\n\nThe syntax predicates are\nis_operator_combination ,\nis_unary_operator_combination , and\nis_binary_operator_combination ,\nand the selectors are\noperator_symbol ,\nfirst_operand , and\nsecond_operand.\n\n```javascript\noperator_combination\n\nfunction is_operator_combination(component) {\n return is_unary_operator_combination(component) ||\n is_binary_operator_combination(component);\n}\nfunction is_unary_operator_combination(component) {\n return is_tagged_list(component, \"unary_operator_combination\");\n}\nfunction is_binary_operator_combination(component) {\n return is_tagged_list(component, \"binary_operator_combination\");\n}\nfunction operator_symbol(component) {\n return list_ref(component, 1);\n}\nfunction first_operand(component) {\n return list_ref(component, 2);\n}\nfunction second_operand(component) {\n return list_ref(component, 3);\n}\n```\n\nThe evaluator uses operator_combination_to_application to transform an\n\n```javascript\noperator_combination_to_application\n\t operator_combination\n\t make_application\n\nfunction operator_combination_to_application(component) {\n const operator = operator_symbol(component);\n return is_unary_operator_combination(component)\n ? make_application(make_name(operator),\n list(first_operand(component)))\n : make_application(make_name(operator),\n list(first_operand(component),\n second_operand(component)));\n}\n```\n\nComponents (such as function declarations and operator combinations) that we\nchoose\nto implement as syntactic transformations are called\nderived components.\n\nLogical composition operations are also\nderived components (see exercise ).\n\nFor example, recall the imperative-style version of the iterative factorial function from section :", - "token_count": 300, + "content": "The syntax predicate\nis_function_declaration\nrecognizes these.\n\n```javascript\nfunction_declaration_syntax\n tagged_list\n\nfunction is_function_declaration(component) {\n return is_tagged_list(component, \"function_declaration\");\n}\nfunction function_declaration_name(component) {\n return list_ref(component, 1);\n}\nfunction function_declaration_parameters(component) {\n return list_ref(component, 2);\n}\nfunction function_declaration_body(component) {\n return list_ref(component, 3);\n}\n```\n\nThe syntax predicate is_declaration returns true for all three kinds of declarations.\n\n```javascript\ndefinition\n\t tagged_list\n\t definition_example\n\nfunction is_declaration(component) {\n return is_tagged_list(component, \"constant_declaration\") ||\n is_tagged_list(component, \"variable_declaration\") ||\n is_tagged_list(component, \"function_declaration\");\n}\n```\n\n```javascript\ndefinition_example\n\nconst my_declaration_statement = parse(\"let x = 1;\");\ndisplay(is_declaration(my_declaration_statement));\ndisplay(declaration_symbol(my_declaration_statement));\ndisplay(declaration_value_expression(my_declaration_statement));\n```\n\nSome\nsyntactic forms in our language can be defined in terms of\ncomponents involving other syntactic forms, rather than being\nimplemented directly.\n\nOne example is\nfunction declaration, which\nevaluate\ntransforms into a constant declaration whose\nvalue expression is a lambda expression.\n\n```javascript\nfunction_declaration\n\t tagged_list\n\nfunction function_decl_to_constant_decl(component) {\n return make_constant_declaration(\n function_declaration_name(component),\n make_lambda_expression(\n function_declaration_parameters(component),\n function_declaration_body(component)));\n}\n\nfunction is_function_declaration(component) {\n return is_tagged_list(component, \"function_declaration\");\n}\nfunction function_declaration_name(component) {\n return list_ref(component, 1);\n}\nfunction function_declaration_parameters(component) {\n return list_ref(component, 2);\n}\nfunction function_declaration_body(component) {\n return list_ref(component, 3);\n}\nfunction function_decl_to_constant_decl(component) {\n return make_constant_declaration(\n function_declaration_name(component),\n make_lambda_expression(\n function_declaration_parameters(component),\n function_declaration_body(component)));\n}\n```\n\nImplementing the evaluation of function declarations in this way simplifies the evaluator because it reduces the number of syntactic forms for which the evaluation process\n\nmust be explicitly specified.\n\nSimilarly, we define\noperator combinations in terms of\nfunction applications.\n\nOperator combinations are unary or binary and carry their operator symbol as second element\nin the tagged-list representation:\n\n```javascript\n$\\ll\\ $unary-operator expression$\\ \\gg$ =\n list(\"unary_operator_combination\",\n \"unary-operator\",\n list($\\ll\\ $expression$\\ \\gg$))\n```\n\nwhere unary-operator is\n!\n\n(for logical negation) or\n-unary (for numeric negation), and\n\n```javascript\n$\\ll\\ $expression$_1$ binary-operator expression$_2\\;\\gg$ =\n list(\"binary_operator_combination\",\n \"binary-operator\",\n list($\\ll\\ $expression$_1\\;\\gg$, $\\ll\\ $expression$_2\\;\\gg$))\n```\n\nwhere binary-operator is\n+ ,\n- ,\n* ,\n/ ,\n% ,\n=== ,\n!== ,\n> ,\n< ,\n>= or\n<=.", + "token_count": 293, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2750,8 +4010,8 @@ "chunk_id": "Metalinguistic_Abstraction_Representing_Components_7" }, { - "content": "For example, recall the imperative-style version of the iterative factorial function from section :\n\n```javascript\nfactorial_imperative_2\n\t factorial_example\n\t 120\n\nfunction factorial(n) {\n let product = 1;\n let counter = 1;\n function iter() {\n if (counter > n) {\n return product;\n } else {\n product = counter * product;\n counter = counter + 1;\n return iter();\n }\n }\n return iter();\n}\n```\n\n\\newpage\\noindent We can formulate the same algorithm using a while loop as follows:\n\n```javascript\nfactorial_with_loop\n\nfunction factorial(n) {\n let product = 1;\n let counter = 1;\n while (counter <= n) {\n product = counter * product;\n counter = counter + 1;\n }\n return product;\n}\n```\n\nWhile loops are parsed as follows:\n\n```javascript\n$\\ll\\ $while (predicate) block$\\ \\gg$ =\n list(\"while_loop\", $\\ll\\ $predicate$\\ \\gg$, $\\ll\\ $block$\\ \\gg$)\n```\n\nFor such a program, JavaScript\nstatically\ndistinguishes between value-producing and\nnon-value-producing statements.\n\n(Here\nstatically means that\nwe can make the distinction by inspecting the program\nrather than by running it.)\nAll declarations are\nnon-value-producing, and all\nStatements and conditional statements are\nvalue-producing.\n\nThe value of an statement is the value of the expression.\n\nThe value of a conditional statement is the value of the branch that\ngets executed, or the value\nundefined if that branch is\nnot value-producing.\n\nA block is value-producing if its body (sequence of statements)\nis value-producing, and then its value is the value of its body.\n\nA sequence is value-producing if any of\nits component statements is value-producing, and then its value is\nthe value of its last value-producing component statement.\n\nFinally, if the whole\nprogram is not value-producing, its value is the value\nundefined.\n-\n-\nAccording to this specification, what are the values of the\nfollowing four programs?", - "token_count": 281, + "content": "where binary-operator is\n+ ,\n- ,\n* ,\n/ ,\n% ,\n=== ,\n!== ,\n> ,\n< ,\n>= or\n<=.\n\n```javascript\noperator_combination\n\nfunction is_operator_combination(component) {\n return is_unary_operator_combination(component) ||\n is_binary_operator_combination(component);\n}\nfunction is_unary_operator_combination(component) {\n return is_tagged_list(component, \"unary_operator_combination\");\n}\nfunction is_binary_operator_combination(component) {\n return is_tagged_list(component, \"binary_operator_combination\");\n}\nfunction operator_symbol(component) {\n return list_ref(component, 1);\n}\nfunction first_operand(component) {\n return list_ref(component, 2);\n}\nfunction second_operand(component) {\n return list_ref(component, 3);\n}\n```\n\nThe evaluator uses operator_combination_to_application to transform an operator combination into a function application whose function expression is the name of the operator:\n\n```javascript\noperator_combination_to_application\n\t operator_combination\n\t make_application\n\nfunction operator_combination_to_application(component) {\n const operator = operator_symbol(component);\n return is_unary_operator_combination(component)\n ? make_application(make_name(operator),\n list(first_operand(component)))\n : make_application(make_name(operator),\n list(first_operand(component),\n second_operand(component)));\n}\n```\n\nComponents (such as function declarations and operator combinations) that we\nchoose\nto implement as syntactic transformations are called\nderived components.\n\nLogical composition operations are also\nderived components (see exercise ).\n\nFor example, recall the imperative-style version of the iterative factorial function from section :\n\n```javascript\nfactorial_imperative_2\n\t factorial_example\n\t 120\n\nfunction factorial(n) {\n let product = 1;\n let counter = 1;\n function iter() {\n if (counter > n) {\n return product;\n } else {\n product = counter * product;\n counter = counter + 1;\n return iter();\n }\n }\n return iter();\n}\n```\n\n\\newpage\\noindent We can formulate the same algorithm using a while loop as follows:\n\n```javascript\nfactorial_with_loop\n\nfunction factorial(n) {\n let product = 1;\n let counter = 1;\n while (counter <= n) {\n product = counter * product;\n counter = counter + 1;\n }\n return product;\n}\n```\n\nWhile loops are parsed as follows:\n\n```javascript\n$\\ll\\ $while (predicate) block$\\ \\gg$ =\n list(\"while_loop\", $\\ll\\ $predicate$\\ \\gg$, $\\ll\\ $block$\\ \\gg$)\n```\n\nFor such a program, JavaScript\nstatically\ndistinguishes between value-producing and\nnon-value-producing statements.", + "token_count": 283, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2760,8 +4020,8 @@ "chunk_id": "Metalinguistic_Abstraction_Representing_Components_8" }, { - "content": "Finally, if the whole\nprogram is not value-producing, its value is the value\nundefined.\n-\n-\nAccording to this specification, what are the values of the\nfollowing four programs?\n\n```javascript\n1; 2; 3;\n\n1; { if (true) {} else { 2; } }\n\n1; const x = 2;\n\n1; { let x = 2; { x = x + 3; } }\n```\n\n- - Modify the evaluator to adhere to this specification.", - "token_count": 73, + "content": "For such a program, JavaScript\nstatically\ndistinguishes between value-producing and\nnon-value-producing statements.\n\nThe value of an statement is the value of the expression.\n\nThe value of a conditional statement is the value of the branch that\ngets executed, or the value\nundefined if that branch is\nnot value-producing.\n\nA block is value-producing if its body (sequence of statements)\nis value-producing, and then its value is the value of its body.\n\nA sequence is value-producing if any of\nits component statements is value-producing, and then its value is\nthe value of its last value-producing component statement.\n\nFinally, if the whole\nprogram is not value-producing, its value is the value\nundefined.\n-\n-\nAccording to this specification, what are the values of the\nfollowing four programs?\n\n```javascript\n1; 2; 3;\n\n1; { if (true) {} else { 2; } }\n\n1; const x = 2;\n\n1; { let x = 2; { x = x + 3; } }\n```\n\n- - Modify the evaluator to adhere to this specification.", + "token_count": 168, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2770,8 +4030,8 @@ "chunk_id": "Metalinguistic_Abstraction_Representing_Components_9" }, { - "content": "Given the evaluator, we have in our hands a description\n(expressed in JavaScript)\nof the process by which\nJavaScript statements and expressions\nare evaluated.\n\nOne advantage of expressing the evaluator as a program is\nthat we can run the program.\n\nThis gives us, running within\nJavaScript,\na working model of how\nJavaScript\nitself evaluates expressions.\n\nThis can serve as a framework for\nexperimenting with evaluation rules, as we shall do later in this chapter.\n\nOur evaluator program reduces expressions ultimately to the application of\nprimitive\nfunctions.\n\nTherefore, all that we need to run the evaluator is to create a mechanism\nthat calls on the underlying\nJavaScript\nsystem to model the application of primitive\nfunctions.\n\nThere must be a binding for each primitive function name and operator, so that when evaluate evaluates the function expression of an application of a\n\nprimitive, it will find an object to pass to functions and operators that can appear in the expressions we will be evaluating.\n\n```javascript\nThe global environment also includes bindings for\n\tundefined\n\tand other names,\n```\n\nso that they can be used as constants in expressions to be evaluated.\n\n```javascript\nheadline_4_1_4\n\n// functions from SICP JS 4.1.4\n```\n\n```javascript\nsetup_environment\n setup_environment_example\n tagged_list\n extend_environment\n enclosing_environment\n make_frame\n primitive_constants\n primitive_procedures\n primitive_procedure\n '/'\n\nfunction setup_environment() {\n return extend_environment(append(primitive_function_symbols,\n primitive_constant_symbols),\n append(primitive_function_objects,\n primitive_constant_values),\n the_empty_environment);\n}\n```\n\n```javascript\nsetup_environment_example\n\nconst the_global_environment = setup_environment();\n\nthe_global_environment;\n\nconst the_global_environment = setup_environment();\n\nlist_ref(head(head(the_global_environment)), 12);\n```\n\n```javascript\nthe_global_environment\n setup_environment\n the_global_environment_example\n\nconst the_global_environment = setup_environment();\n```\n\n```javascript\nthe_global_environment_example\n\nthe_global_environment;\n```\n\nIt does not matter how we represent primitive function objects, so long\nas is_primitive_function\nand apply_primitive_function.\n\nWe\nhave chosen to represent a primitive function as a list beginning with\nthe string \"primitive\" and\ncontaining a function in the underlying JavaScript that implements that\nprimitive.\n\n```javascript\nprimitive_procedure\n\t tagged_list\n\t primitive_procedure_example\n\t true\n\nfunction is_primitive_function(fun) {\n return is_tagged_list(fun, \"primitive\");\n}\n\nfunction primitive_implementation(fun) { return head(tail(fun)); }\n```", - "token_count": 308, + "content": "Given the evaluator, we have in our hands a description\n(expressed in JavaScript)\nof the process by which\nJavaScript statements and expressions\nare evaluated.\n\nOne advantage of expressing the evaluator as a program is\nthat we can run the program.\n\nThis gives us, running within\nJavaScript,\na working model of how\nJavaScript\nitself evaluates expressions.\n\nThis can serve as a framework for\nexperimenting with evaluation rules, as we shall do later in this chapter.\n\nOur evaluator program reduces expressions ultimately to the application of\nprimitive\nfunctions.\n\nTherefore, all that we need to run the evaluator is to create a mechanism\nthat calls on the underlying\nJavaScript\nsystem to model the application of primitive\nfunctions.\n\nThere must be a binding for each primitive\nfunction\nname and operator, so that when\nevaluate\nevaluates the\nfunction expression\nof an application of a primitive, it will find an\nobject to pass to.\n\nWe thus set up a\nglobal environment that associates unique objects with the names of the\nprimitive\nfunctions and operators\nthat can appear in the expressions we will be evaluating.\n\nThe global environment also includes bindings for undefined and other names,\nso that they can be used as constants in expressions to be evaluated.\n\n```javascript\nheadline_4_1_4\n\n// functions from SICP JS 4.1.4\n```\n\n```javascript\nsetup_environment\n setup_environment_example\n tagged_list\n extend_environment\n enclosing_environment\n make_frame\n primitive_constants\n primitive_procedures\n primitive_procedure\n '/'\n\nfunction setup_environment() {\n return extend_environment(append(primitive_function_symbols,\n primitive_constant_symbols),\n append(primitive_function_objects,\n primitive_constant_values),\n the_empty_environment);\n}\n```\n\n```javascript\nsetup_environment_example\n\nconst the_global_environment = setup_environment();\n\nthe_global_environment;\n\nconst the_global_environment = setup_environment();\n\nlist_ref(head(head(the_global_environment)), 12);\n```\n\n```javascript\nthe_global_environment\n setup_environment\n the_global_environment_example\n\nconst the_global_environment = setup_environment();\n```\n\n```javascript\nthe_global_environment_example\n\nthe_global_environment;\n```\n\nIt does not matter how we represent primitive function objects, so long\nas can identify and apply them using\nthe functions is_primitive_function\nand apply_primitive_function.", + "token_count": 284, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2780,8 +4040,8 @@ "chunk_id": "Metalinguistic_Abstraction_Running_the_Evaluator_as_a_Program_1" }, { - "content": "We\nhave chosen to represent a primitive function as a list beginning with\nthe string \"primitive\" and\ncontaining a function in the underlying JavaScript that implements that\nprimitive.\n\n```javascript\nprimitive_procedure_example\n\nconst my_primitive_plus =\n list(\"primitive\", (x, y) => x + y );\ndisplay(is_primitive_function(my_primitive_plus));\ndisplay(primitive_implementation(my_primitive_plus));\n\nconst my_primitive_plus =\n list(\"primitive\", (x, y) => x + y );\nprimitive_implementation(my_primitive_plus);\nis_primitive_function(my_primitive_plus);\n```\n\n```javascript\nThe function\n setup_environment\n```\n\nwill get the primitive names and implementation functions from a list:\n\n```javascript\nprimitive_procedures\n primitive_procedures_example\n 20\n\nconst primitive_functions = list(list(\"head\", head ),\n list(\"tail\", tail ),\n list(\"pair\", pair ),\n list(\"is_null\", is_null ),\n list(\"+\", (x, y) => x + y ),\n more primitive functions\n );\n\nconst primitive_function_symbols =\n map(f => head(f), primitive_functions);\n\nconst primitive_function_objects =\n map(f => list(\"primitive\", head(tail(f))),\n primitive_functions);\n\nconst primitive_functions = list(\n list(\"head\", head ),\n list(\"tail\", tail ),\n list(\"pair\", pair ),\n list(\"list\", list ),\n list(\"is_null\", is_null ),\n list(\"display\", display ),\n list(\"error\", error ),\n list(\"math_abs\",math_abs ),\n list(\"+\", (x, y) => x + y ),\n list(\"-\", (x, y) => x - y ),\n list(\"-unary\", x => - x ),\n list(\"*\", (x, y) => x * y ),\n list(\"/\", (x, y) => x / y ),\n list(\"%\", (x, y) => x % y ),\n list(\"===\", (x, y) => x === y),\n list(\"!==\", (x, y) => x !== y),\n list(\"<\", (x, y) => x < y),\n list(\"<=\", (x, y) => x <= y),\n list(\">\", (x, y) => x > y),\n list(\">=\", (x, y) => x >= y),\n list(\"!\", x => ! x)\n );\nconst primitive_function_symbols =\n map(head, primitive_functions);\nconst primitive_function_objects =\n map(fun => list(\"primitive\", head(tail(fun))),\n primitive_functions);\n```\n\n```javascript\nprimitive_procedures_example\n\nprimitive_functions;\n\nlength(primitive_functions);\n```\n\nSimilar to primitive functions, we define other primitive constants that are installed in the global environment by the function setup_environment.", - "token_count": 279, + "content": "It does not matter how we represent primitive function objects, so long\nas can identify and apply them using\nthe functions is_primitive_function\nand apply_primitive_function.\n\n```javascript\nprimitive_procedure\n\t tagged_list\n\t primitive_procedure_example\n\t true\n\nfunction is_primitive_function(fun) {\n return is_tagged_list(fun, \"primitive\");\n}\n\nfunction primitive_implementation(fun) { return head(tail(fun)); }\n```\n\n```javascript\nprimitive_procedure_example\n\nconst my_primitive_plus =\n list(\"primitive\", (x, y) => x + y );\ndisplay(is_primitive_function(my_primitive_plus));\ndisplay(primitive_implementation(my_primitive_plus));\n\nconst my_primitive_plus =\n list(\"primitive\", (x, y) => x + y );\nprimitive_implementation(my_primitive_plus);\nis_primitive_function(my_primitive_plus);\n```\n\nThe function setup_environment will get the primitive names and implementation functions from a list:\n\n```javascript\nprimitive_procedures\n primitive_procedures_example\n 20\n\nconst primitive_functions = list(list(\"head\", head ),\n list(\"tail\", tail ),\n list(\"pair\", pair ),\n list(\"is_null\", is_null ),\n list(\"+\", (x, y) => x + y ),\n more primitive functions\n );\n\nconst primitive_function_symbols =\n map(f => head(f), primitive_functions);\n\nconst primitive_function_objects =\n map(f => list(\"primitive\", head(tail(f))),\n primitive_functions);\n\nconst primitive_functions = list(\n list(\"head\", head ),\n list(\"tail\", tail ),\n list(\"pair\", pair ),\n list(\"list\", list ),\n list(\"is_null\", is_null ),\n list(\"display\", display ),\n list(\"error\", error ),\n list(\"math_abs\",math_abs ),\n list(\"+\", (x, y) => x + y ),\n list(\"-\", (x, y) => x - y ),\n list(\"-unary\", x => - x ),\n list(\"*\", (x, y) => x * y ),\n list(\"/\", (x, y) => x / y ),\n list(\"%\", (x, y) => x % y ),\n list(\"===\", (x, y) => x === y),\n list(\"!==\", (x, y) => x !== y),\n list(\"<\", (x, y) => x < y),\n list(\"<=\", (x, y) => x <= y),\n list(\">\", (x, y) => x > y),\n list(\">=\", (x, y) => x >= y),\n list(\"!\", x => ! x)\n );\nconst primitive_function_symbols =\n map(head, primitive_functions);\nconst primitive_function_objects =\n map(fun => list(\"primitive\", head(tail(fun))),\n primitive_functions);\n```\n\n```javascript\nprimitive_procedures_example\n\nprimitive_functions;\n\nlength(primitive_functions);\n```\n\nSimilar to primitive functions, we define other primitive constants that are installed in the global environment by the function setup_environment.", + "token_count": 292, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2790,8 +4050,8 @@ "chunk_id": "Metalinguistic_Abstraction_Running_the_Evaluator_as_a_Program_2" }, { - "content": "Similar to primitive functions, we define other primitive constants that are installed in the global environment by the function setup_environment.\n\n```javascript\nprimitive_constants\n\t primitive_constants_example\n\t 5\n\nconst primitive_constants = list(list(\"undefined\", undefined),\n list(\"math_PI\", math_PI)\n more primitive constants\n );\n\nconst primitive_constant_symbols =\n map(c => head(c), primitive_constants);\n\nconst primitive_constant_values =\n map(c => head(tail(c)), primitive_constants);\n\nconst primitive_constants = list(list(\"undefined\", undefined),\n list(\"Infinity\", Infinity),\n list(\"math_PI\", math_PI),\n list(\"math_E\", math_E),\n list(\"NaN\", NaN)\n );\nconst primitive_constant_symbols =\n map(c => head(c), primitive_constants);\nconst primitive_constant_values =\n map(c => head(tail(c)), primitive_constants);\n```\n\n```javascript\nprimitive_constants_example\n\nprimitive_constants;\n\nlength(primitive_constants);\n```\n\nTo apply a primitive function, we simply apply the implementation function to the arguments, using the underlying JavaScript system:\n\n```javascript\napply_primitive_procedure\n apply_primitive_procedure_example\n 3\n\nfunction apply_primitive_function(fun, arglist) {\n return apply_in_underlying_javascript(\n primitive_implementation(fun), arglist);\n}\n```\n\n```javascript\napply_primitive_procedure_example\n primitive_procedure\n primitive_procedure_example\n\napply_primitive_function(my_primitive_plus, list(1, 2));\n```", - "token_count": 124, + "content": "Similar to primitive functions, we define other primitive constants that are installed in the global environment by the function setup_environment.\n\n```javascript\nprimitive_constants_example\n\nprimitive_constants;\n\nlength(primitive_constants);\n```\n\nTo apply a primitive function, we simply apply the implementation function to the arguments, using the underlying JavaScript system:\n\n```javascript\napply_primitive_procedure\n apply_primitive_procedure_example\n 3\n\nfunction apply_primitive_function(fun, arglist) {\n return apply_in_underlying_javascript(\n primitive_implementation(fun), arglist);\n}\n```\n\n```javascript\napply_primitive_procedure_example\n primitive_procedure\n primitive_procedure_example\n\napply_primitive_function(my_primitive_plus, list(1, 2));\n```\n\nFor convenience in running the metacircular evaluator, we provide a driver loop that models the read-evaluate-print loop of the underlying JavaScript system.\n\nIt prints a prompt and reads an input program as a string.\n\nIt transforms the program string into a tagged-list representation of the statement as described in sectiona process called parsing and accomplished by the primitive function parse.\n\nWe precede each printed result by an output prompt so as to distinguish the value of the program from other output that may be printed.\n\nThe driver loop gets the program environment of the previous program as argument.", + "token_count": 164, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2800,8 +4060,8 @@ "chunk_id": "Metalinguistic_Abstraction_Running_the_Evaluator_as_a_Program_3" }, { - "content": "To apply a primitive function, we simply apply the implementation function to the arguments, using the underlying JavaScript system:\n\n```javascript\nFor convenience in running the metacircular evaluator, we provide a\n\tdriver loop that models the read-evaluate-print loop of\n\tthe underlying JavaScript system. It prints a\n\tprompt and reads an input program as a string.\n\tIt transforms the program string\n\tinto a tagged-list representation of the statement as described in\n\tsectiona\n\tprocess called parsing and accomplished by the primitive function\n\tparse.\n\tWe precede each printed result by\n\tan output prompt so as to distinguish the value of the\n\tprogram from other output that may be printed. The driver loop gets\n\tthe program environment of the previous program as argument.\n\tAs described at the end of section, the\n\tdriver loop treats the program as if it were in a block: It\n\tscans out the declarations, extends the given environment by a frame\n\tcontaining a binding of each name to\n\t\"*unassigned*\", and evaluates\n\tthe program with respect to the extended environment, which\n\tis then passed as argument to the next iteration of the driver loop.\n\n\t driver_loop\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\t user_print\n\t user_read\n\t driver_loop_example\n\nconst input_prompt = \"M-evaluate input: \";\nconst output_prompt = \"M-evaluate value: \";\n\nfunction driver_loop(env) {\n const input = user_read(input_prompt);\n if (is_null(input)) {\n display(\"evaluator terminated\");\n } else {\n const program = parse(input);\n const locals = scan_out_declarations(program);\n const unassigneds = list_of_unassigned(locals);\n const program_env = extend_environment(locals, unassigneds, env);\n const output = evaluate(program, program_env);\n user_print(output_prompt, output);\n return driver_loop(program_env);\n }\n}\n\nconst input_prompt = \"\\nM-evaluate input:\\n\";\nconst output_prompt = \"\\nM-evaluate value:\\n\";\n\nfunction driver_loop(env, history) {\n const input = user_read(history);\n if (is_null(input)) {\n display(\"\", history + \"\\n--- session end ---\\n\");\n } else {\n const program = parse(input);\n const locals = scan_out_declarations(program);\n const unassigneds = list_of_unassigned(locals);\n const program_env = extend_environment(\n locals, unassigneds, env);\n const output = evaluate(program, program_env);\n const new_history = history +\n input_prompt +\n input +\n output_prompt +\n to_string(output);\n return driver_loop(program_env, new_history);\n }\n}\n\n\"metacircular evaluator loaded\";\n```", - "token_count": 322, + "content": "The driver loop gets the program environment of the previous program as argument.\n\nWe use a special printing\nfunction user_print,\nto avoid printing the environment part of a compound\nfunction,\nwhich may be a very long list (or may even contain cycles).\n\n```javascript\nuser_print\n user_print_example\n\nfunction user_print(string, object) {\n function prepare(object) {\n return is_compound_function(object)\n ? \"< compound-function >\"\n : is_primitive_function(object)\n ? \"< primitive-function >\"\n : is_pair(object)\n ? pair(prepare(head(object)),\n prepare(tail(object)))\n : object;\n }\n display(string + \" \" + stringify(prepare(object)));\n}\n\nfunction to_string(object) {\n return is_compound_function(object)\n ? \"\"\n : is_primitive_function(object)\n ? \"\"\n : is_pair(object)\n ? \"[\" + to_string(head(object)) + \", \"\n + to_string(tail(object)) + \"]\"\n : stringify(object);\n}\n\nfunction user_print(prompt_string, object) {\n display(\"----------------------------\",\n prompt_string + \"\\n\" + to_string(object) + \"\\n\");\n}\n```\n\n```javascript\nuser_print_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nuser_print(\"output: \",\n evaluate(parse(\"1 + 2;\"),\n the_global_environment));\n```\n\nNow all we need to do to run the evaluator is to initialize the global\nenvironment and start the driver loop.\n\nHere is a sample interaction:\n\n```javascript\ndriver_loop_example\n driver_loop\n\nconst the_global_environment = setup_environment();\ndriver_loop(the_global_environment);\n\ndriver_loop(the_global_environment, \"--- session start ---\");\n```\n\n```javascript\nmeta_append\n driver_loop\n driver_loop_example\n\nM-evaluate input:\n\nfunction append(xs, ys) {\n return is_null(xs)\n ? ys\n : pair(head(xs), append(tail(xs), ys));\n}\n\n// press \"Run\" to start the driver loop\n// M-evaluate input:\n// function append(xs, ys) { return is_null(xs) ? ys : pair(head(xs), append(tail(xs), ys)); }\n// M-evaluate value:\n// undefined\n\n// M-evaluate input:\n// append(list(\"a\", \"b\", \"c\"), list(\"d\", \"e\", \"f\"));\n// M-evaluate value:\n// [\"a\", [\"b\", [\"c\", [\"d\", [\"e\", [\"f\", null]]]]]]\n\nM-evaluate value:\nundefined\n```", + "token_count": 247, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2810,9 +4070,9 @@ "chunk_id": "Metalinguistic_Abstraction_Running_the_Evaluator_as_a_Program_4" }, { - "content": "To apply a primitive function, we simply apply the implementation function to the arguments, using the underlying JavaScript system:\n\n```javascript\nWe use JavaScript's prompt function\n\tto request and read the input string from the user:\n\n\t user_read\n\nfunction user_read(prompt_string) {\n return prompt(prompt_string);\n}\n```\n\nThe function\nprompt returns\nnull when the user cancels the\ninput.\n\nWe use a special printing\nfunction user_print,\nto avoid printing the environment part of a compound\nfunction,\nwhich may be a very long list (or may even contain cycles).\n\n```javascript\nuser_print\n user_print_example\n\nfunction user_print(string, object) {\n function prepare(object) {\n return is_compound_function(object)\n ? \"< compound-function >\"\n : is_primitive_function(object)\n ? \"< primitive-function >\"\n : is_pair(object)\n ? pair(prepare(head(object)),\n prepare(tail(object)))\n : object;\n }\n display(string + \" \" + stringify(prepare(object)));\n}\n\nfunction to_string(object) {\n return is_compound_function(object)\n ? \"\"\n : is_primitive_function(object)\n ? \"\"\n : is_pair(object)\n ? \"[\" + to_string(head(object)) + \", \"\n + to_string(tail(object)) + \"]\"\n : stringify(object);\n}\n\nfunction user_print(prompt_string, object) {\n display(\"----------------------------\",\n prompt_string + \"\\n\" + to_string(object) + \"\\n\");\n}\n```\n\n```javascript\nuser_print_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nuser_print(\"output: \",\n evaluate(parse(\"1 + 2;\"),\n the_global_environment));\n```\n\nNow all we need to do to run the evaluator is to initialize the global\nenvironment and start the driver loop.\n\nHere is a sample interaction:\n\n```javascript\ndriver_loop_example\n driver_loop\n\nconst the_global_environment = setup_environment();\ndriver_loop(the_global_environment);\n\ndriver_loop(the_global_environment, \"--- session start ---\");\n```\n\n```javascript\nmeta_append\n driver_loop\n driver_loop_example\n\nM-evaluate input:\n\nfunction append(xs, ys) {\n return is_null(xs)\n ? ys\n : pair(head(xs), append(tail(xs), ys));\n}\n\n// press \"Run\" to start the driver loop\n// M-evaluate input:\n// function append(xs, ys) { return is_null(xs) ? ys : pair(head(xs), append(tail(xs), ys)); }\n// M-evaluate value:\n// undefined\n\n// M-evaluate input:\n// append(list(\"a\", \"b\", \"c\"), list(\"d\", \"e\", \"f\"));\n// M-evaluate value:\n// [\"a\", [\"b\", [\"c\", [\"d\", [\"e\", [\"f\", null]]]]]]\n```", - "token_count": 285, - "has_code": true, + "content": "Here is a sample interaction:", + "token_count": 5, + "has_code": false, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", "subsection": "Running the Evaluator as a Program", @@ -2820,18 +4080,8 @@ "chunk_id": "Metalinguistic_Abstraction_Running_the_Evaluator_as_a_Program_5" }, { - "content": "Here is a sample interaction:\n\n```javascript\nmeta_append_example\n driver_loop\n driver_loop_example\n\nM-evaluate input:\n\nappend(list(\"a\", \"b\", \"c\"), list(\"d\", \"e\", \"f\"));\n\n// press \"Run\" to start the driver loop\n// M-evaluate input:\n// function append(xs, ys) { return is_null(xs) ? ys : pair(head(xs), append(tail(xs), ys)); }\n// M-evaluate value:\n// undefined\n\n// M-evaluate input:\n// append(list(\"a\", \"b\", \"c\"), list(\"d\", \"e\", \"f\"));\n// M-evaluate value:\n// [\"a\", [\"b\", [\"c\", [\"d\", [\"e\", [\"f\", null]]]]]]\n```", - "token_count": 69, - "has_code": true, - "chapter": "Metalinguistic Abstraction", - "section": "The Metacircular Evaluator", - "subsection": "Running the Evaluator as a Program", - "chunk_index": 6, - "chunk_id": "Metalinguistic_Abstraction_Running_the_Evaluator_as_a_Program_6" - }, - { - "content": "The evaluator implemented above is simple, but it is very\ncomponents\nis interleaved\nwith their execution.\n\nThus if a program is executed many times, its\nsyntax is analyzed many times.\n\nConsider, for example, evaluating\nfactorial(4)\nusing the following definition of\n\n```javascript\nfactorial_4_1_7\n factorial_example\n 120\n\nfunction factorial(n) {\n return n === 1\n ? 1\n : factorial(n - 1) * n;\n}\n```\n\nEach time\na conditional\nexpression and extract the predicate.\n\nOnly then can it evaluate the\npredicate and dispatch on its value.\n\nEach time it evaluates the expression\nfactorial(n - 1) * n,\nor the subexpressions\nfactorial(n - 1)\nand\nn - 1,\nthe evaluator must perform the case analysis in\nevaluate\nto determine that the expression is an application, and must extract\nits function expression and argument expressions.\n\nThis analysis is expensive.\n\nPerforming it repeatedly is wasteful.\n\nWe can transform the evaluator to be significantly more efficient by\narranging things so that syntactic analysis is performed only\nonce.\nevaluate,\nwhich takes\na component\nand an environment, into two parts.\n\nThe\nfunction\ncomponent.\n\nIt performs the syntactic\nanalysis and returns a new\nfunction , the\nexecution\nfunction , that\nencapsulates the work to be done in executing the analyzed\ncomponent.\n\nThe execution\nfunction\ntakes an environment as its\nargument and completes the evaluation.\n\nThis saves work because\na component,\nwhile the execution\nfunction\nmay be called many times.\n\nWith the separation into analysis and execution, evaluate now becomes\n\n```javascript\nanalyze_headline\n\n// functions from SICP JS 4.1.7\n```\n\n```javascript\nevaluate_4_1_7\n analyze\n evaluate_4_1_7_simple_function\n\nfunction evaluate(component, env) {\n return analyze(component)(env);\n}\n```\n\n```javascript\nevaluate_4_1_7_simple_function\n evaluate_4_1_7\n 5\n\nevaluate(parse(\"{ function f(x) { return x + 1; } f(4); }\"),\n the_global_environment);\n```\n\nThe result of calling\nfunction\nto be applied to the environment.", - "token_count": 287, + "content": "The evaluator implemented above is simple, but it is very\ninefficient, because the syntactic analysis of\ncomponents\nis interleaved\nwith their execution.\n\nThus if a program is executed many times, its\nsyntax is analyzed many times.\n\nConsider, for example, evaluating\nfactorial(4)\nusing the following definition of\n:\n\n```javascript\nfactorial_4_1_7\n factorial_example\n 120\n\nfunction factorial(n) {\n return n === 1\n ? 1\n : factorial(n - 1) * n;\n}\n```\n\nEach time is called, the evaluator\nmust determine that the body is\na conditional\nexpression and extract the predicate.\n\nOnly then can it evaluate the\npredicate and dispatch on its value.\n\nEach time it evaluates the expression\nfactorial(n - 1) * n,\nor the subexpressions\nfactorial(n - 1)\nand\nn - 1,\nthe evaluator must perform the case analysis in\nevaluate\nto determine that the expression is an application, and must extract\nits function expression and argument expressions.\n\nThis analysis is expensive.\n\nPerforming it repeatedly is wasteful.\n\nWe can transform the evaluator to be significantly more efficient by\narranging things so that syntactic analysis is performed only\nonce.\n\nWe split\nevaluate,\nwhich takes\na component\nand an environment, into two parts.\n\nThe\nfunction\ntakes only the\ncomponent.\n\nIt performs the syntactic\nanalysis and returns a new\nfunction , the\nexecution\nfunction , that\nencapsulates the work to be done in executing the analyzed\ncomponent.\n\nThe execution\nfunction\ntakes an environment as its\nargument and completes the evaluation.\n\nThis saves work because\nwill be called only once on\na component,\nwhile the execution\nfunction\nmay be called many times.\n\nWith the separation into analysis and execution, evaluate now becomes\n\n```javascript\nanalyze_headline\n\n// functions from SICP JS 4.1.7\n```\n\n```javascript\nevaluate_4_1_7\n analyze\n evaluate_4_1_7_simple_function\n\nfunction evaluate(component, env) {\n return analyze(component)(env);\n}\n```\n\n```javascript\nevaluate_4_1_7_simple_function\n evaluate_4_1_7\n 5\n\nevaluate(parse(\"{ function f(x) { return x + 1; } f(4); }\"),\n the_global_environment);\n```", + "token_count": 304, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2840,8 +4090,8 @@ "chunk_id": "Metalinguistic_Abstraction_Separating_Syntactic_Analysis_from_Execution_1" }, { - "content": "The result of calling\nfunction\nto be applied to the environment.\n\nThe\nfunction\nis the same case analysis as performed by the original\nevaluate\nof section , except that the\nfunctions\nto which we dispatch perform only analysis, not full evaluation:\n\n```javascript\nanalyze_example\n\nanalyze(parse(\"{ const x = 1; x + 1; }\"))\n (the_global_environment);\n```\n\n```javascript\nanalyze\n headline_4_1_1\n list_of_unassigned\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n analyze_headline\n analyze_literal\n analyze_variable\n analyze_assignment\n analyze_if\n scan_out_declarations\n analyze_lambda\n analyze_sequence\n analyze_block\n analyze_return_statement\n analyze_application\n analyze_example\n 2\n\nfunction analyze(component) {\n return is_literal(component)\n ? analyze_literal(component)\n : is_name(component)\n ? analyze_name(component)\n : is_application(component)\n ? analyze_application(component)\n : is_operator_combination(component)\n ? analyze(operator_combination_to_application(component))\n : is_conditional(component)\n ? analyze_conditional(component)\n : is_lambda_expression(component)\n ? analyze_lambda_expression(component)\n : is_sequence(component)\n ? analyze_sequence(sequence_statements(component))\n : is_block(component)\n ? analyze_block(component)\n : is_return_statement(component)\n ? analyze_return_statement(component)\n : is_function_declaration(component)\n ? analyze(function_decl_to_constant_decl(component))\n : is_declaration(component)\n ? analyze_declaration(component)\n : is_assignment(component)\n ? analyze_assignment(component)\n : error(component, \"unknown syntax -- analyze\");\n}\n```\n\nHere is the simplest syntactic analysis\n\n```javascript\nfunction,\n\twhich handles literal expressions.\n```\n\nIt returns an execution function that ignores its environment argument and just returns the value of the literal:\n\n```javascript\nanalyze_literal_example\n\n// null is the empty environment (not used here)\nanalyze_literal(parse(\"true;\"))(null);\n```\n\n```javascript\nanalyze_literal\n functions_4_1_2\n analyze_literal_example\n true\n\nfunction analyze_literal(component) {\n return env => literal_value(component);\n}\n```\n\nLooking up the value of a name must still be done in the execution phase, since this depends upon knowing the environment.\n\n```javascript\nanalyze_variable_example\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nanalyze_name(parse(\"myname;\"))\n (extend_environment(list(\"myname\"), list(1),\n the_global_environment));\n```\n\n```javascript\nanalyze_variable\n analyze_variable_example\n 1\n\nfunction analyze_name(component) {\n return env => lookup_symbol_value(symbol_of_name(component), env);\n}\n```\n\nFor conditionals, we extract and analyze the predicate, consequent, and alternative at analysis time.\n\n```javascript\nanalyze_if_example\n\t analyze\n\nanalyze_conditional(parse(\"true ? 3 : 7;\"))\n (the_global_environment);\n```\n\n```javascript\nanalyze_if\n\t analyze_if_example\n\t 3\n\nfunction analyze_conditional(component) {\n const pfun = analyze(conditional_predicate(component));\n const cfun = analyze(conditional_consequent(component));\n const afun = analyze(conditional_alternative(component));\n return env => is_truthy(pfun(env)) ? cfun(env) : afun(env);\n}\n```\n\nAnalyzing a lambda expression also achieves a major gain in efficiency: We analyze the lambda body only once, even though functions resulting from evaluation of", - "token_count": 315, + "content": "With the separation into analysis and execution, evaluate now becomes\n\nThe\nfunction\nis the same case analysis as performed by the original\nevaluate\nof section , except that the\nfunctions\nto which we dispatch perform only analysis, not full evaluation:\n\n```javascript\nanalyze_example\n\nanalyze(parse(\"{ const x = 1; x + 1; }\"))\n (the_global_environment);\n```\n\n```javascript\nanalyze\n headline_4_1_1\n list_of_unassigned\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n analyze_headline\n analyze_literal\n analyze_variable\n analyze_assignment\n analyze_if\n scan_out_declarations\n analyze_lambda\n analyze_sequence\n analyze_block\n analyze_return_statement\n analyze_application\n analyze_example\n 2\n\nfunction analyze(component) {\n return is_literal(component)\n ? analyze_literal(component)\n : is_name(component)\n ? analyze_name(component)\n : is_application(component)\n ? analyze_application(component)\n : is_operator_combination(component)\n ? analyze(operator_combination_to_application(component))\n : is_conditional(component)\n ? analyze_conditional(component)\n : is_lambda_expression(component)\n ? analyze_lambda_expression(component)\n : is_sequence(component)\n ? analyze_sequence(sequence_statements(component))\n : is_block(component)\n ? analyze_block(component)\n : is_return_statement(component)\n ? analyze_return_statement(component)\n : is_function_declaration(component)\n ? analyze(function_decl_to_constant_decl(component))\n : is_declaration(component)\n ? analyze_declaration(component)\n : is_assignment(component)\n ? analyze_assignment(component)\n : error(component, \"unknown syntax -- analyze\");\n}\n```\n\nHere is the simplest syntactic analysis\nfunction, which handles literal expressions.\n\nIt returns an execution\nfunction\nthat ignores its environment argument and just returns the\nvalue of the literal:\n\n```javascript\nanalyze_literal_example\n\n// null is the empty environment (not used here)\nanalyze_literal(parse(\"true;\"))(null);\n```\n\n```javascript\nanalyze_literal\n functions_4_1_2\n analyze_literal_example\n true\n\nfunction analyze_literal(component) {\n return env => literal_value(component);\n}\n```\n\nLooking up the value of a name must still be done in the execution phase, since this depends upon knowing the environment.\n\n```javascript\nanalyze_variable_example\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nanalyze_name(parse(\"myname;\"))\n (extend_environment(list(\"myname\"), list(1),\n the_global_environment));\n```\n\n```javascript\nanalyze_variable\n analyze_variable_example\n 1\n\nfunction analyze_name(component) {\n return env => lookup_symbol_value(symbol_of_name(component), env);\n}\n```\n\nFor conditionals, we extract and analyze the predicate, consequent, and alternative at analysis time.\n\n```javascript\nanalyze_if_example\n\t analyze\n\nanalyze_conditional(parse(\"true ? 3 : 7;\"))\n (the_global_environment);\n```\n\n```javascript\nanalyze_if\n\t analyze_if_example\n\t 3\n\nfunction analyze_conditional(component) {\n const pfun = analyze(conditional_predicate(component));\n const cfun = analyze(conditional_consequent(component));\n const afun = analyze(conditional_alternative(component));\n return env => is_truthy(pfun(env)) ? cfun(env) : afun(env);\n}\n```\n\nAnalyzing a lambda expression also achieves a major gain in efficiency: We analyze the lambda body only once, even though functions resulting from evaluation of", + "token_count": 312, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2850,8 +4100,8 @@ "chunk_id": "Metalinguistic_Abstraction_Separating_Syntactic_Analysis_from_Execution_2" }, { - "content": "Analyzing a lambda expression also achieves a major gain in efficiency: We analyze the lambda body only once, even though functions resulting from evaluation of\n\nthe lambda expression may be applied many times.\n\n```javascript\nanalyze_lambda_example\n\t analyze\n\nlist_ref(analyze_lambda_expression(parse(\"x => x;\"))\n (the_global_environment),\n 2)\n (extend_environment(list(\"x\"), list(7),\n the_global_environment));\n```\n\n```javascript\nanalyze_lambda\n\t analyze_lambda_example\n\t [ 'return_value', [ 7, null ] ]\n\nfunction analyze_lambda_expression(component) {\n const params = lambda_parameter_symbols(component);\n const bfun = analyze(lambda_body(component));\n return env => make_function(params, bfun, env);\n}\n```\n\nAnalysis of a sequence of statements is more involved.\n\n```javascript\nanalyze_sequence_example\n\t analyze\n\nanalyze_sequence(sequence_statements(parse(\"10; 20; 30;\")))\n (the_global_environment);\n```\n\n```javascript\nanalyze_sequence\n\t analyze_sequence_example\n\t 30\n\nfunction analyze_sequence(stmts) {\n function sequentially(fun1, fun2) {\n return env => {\n const fun1_val = fun1(env);\n return is_return_value(fun1_val)\n ? fun1_val\n : fun2(env);\n };\n }\n function loop(first_fun, rest_funs) {\n return is_null(rest_funs)\n ? first_fun\n : loop(sequentially(first_fun, head(rest_funs)),\n tail(rest_funs));\n }\n const funs = map(analyze, stmts);\n return is_null(funs)\n ? env => undefined\n : loop(head(funs), tail(funs));\n}\n```\n\nThe body of a\nblock is scanned only once for local declarations.\n\nThe bindings are installed in the environment when\nthe execution function for the block is called.\n\n```javascript\nanalyze_block_example\n\t analyze\n\nanalyze_block(parse(\"{ const x = 4; x; }\"))\n (the_global_environment);\n```\n\n```javascript\nanalyze_block\n\t list_of_unassigned\n\t analyze_block_example\n\t 4\n\nfunction analyze_block(component) {\n const body = block_body(component);\n const bfun = analyze(body);\n const locals = scan_out_declarations(body);\n const unassigneds = list_of_unassigned(locals);\n return env => bfun(extend_environment(locals, unassigneds, env));\n}\n```\n\nFor return statements, we analyze the return expression.\n\nThe execution function for the return statement simply calls\nthe execution function for the return expression and wraps\nthe result in a return value.\n\n```javascript\nanalyze_return_statement_example\n\t analyze\n\nanalyze_return_statement(list_ref(parse(\"() => x + 1;\"), 2))\n (extend_environment(list(\"x\"), list(6), the_global_environment));\n```\n\n```javascript\nanalyze_return_statement\n\t analyze_return_statement_example\n\t [ 'return_value', [ 7, null ] ]\n\nfunction analyze_return_statement(component) {\n const rfun = analyze(return_expression(component));\n return env => make_return_value(rfun(env));\n}\n```\n\n```javascript\nThe function\n analyze_assignment\n```\n\nmust defer actually setting the variable until the execution, when the\nenvironment has been supplied.", - "token_count": 307, + "content": "Analyzing a lambda expression also achieves a major gain in efficiency: We analyze the lambda body only once, even though functions resulting from evaluation of\n\n```javascript\nanalyze_lambda_example\n\t analyze\n\nlist_ref(analyze_lambda_expression(parse(\"x => x;\"))\n (the_global_environment),\n 2)\n (extend_environment(list(\"x\"), list(7),\n the_global_environment));\n```\n\n```javascript\nanalyze_lambda\n\t analyze_lambda_example\n\t [ 'return_value', [ 7, null ] ]\n\nfunction analyze_lambda_expression(component) {\n const params = lambda_parameter_symbols(component);\n const bfun = analyze(lambda_body(component));\n return env => make_function(params, bfun, env);\n}\n```\n\nAnalysis of a sequence of\nstatements is more\ninvolved.\n\nEach\nstatement\nin the sequence is analyzed, yielding an execution\nfunction.\n\nThese execution functions\nare combined to produce an execution\nfunction that takes an environment as argument and sequentially\ncalls each individual execution\nfunction with the environment as argument.\n\n```javascript\nanalyze_sequence_example\n\t analyze\n\nanalyze_sequence(sequence_statements(parse(\"10; 20; 30;\")))\n (the_global_environment);\n```\n\n```javascript\nanalyze_sequence\n\t analyze_sequence_example\n\t 30\n\nfunction analyze_sequence(stmts) {\n function sequentially(fun1, fun2) {\n return env => {\n const fun1_val = fun1(env);\n return is_return_value(fun1_val)\n ? fun1_val\n : fun2(env);\n };\n }\n function loop(first_fun, rest_funs) {\n return is_null(rest_funs)\n ? first_fun\n : loop(sequentially(first_fun, head(rest_funs)),\n tail(rest_funs));\n }\n const funs = map(analyze, stmts);\n return is_null(funs)\n ? env => undefined\n : loop(head(funs), tail(funs));\n}\n```\n\nThe body of a\nblock is scanned only once for local declarations.\n\nThe bindings are installed in the environment when\nthe execution function for the block is called.\n\n```javascript\nanalyze_block_example\n\t analyze\n\nanalyze_block(parse(\"{ const x = 4; x; }\"))\n (the_global_environment);\n```\n\n```javascript\nanalyze_block\n\t list_of_unassigned\n\t analyze_block_example\n\t 4\n\nfunction analyze_block(component) {\n const body = block_body(component);\n const bfun = analyze(body);\n const locals = scan_out_declarations(body);\n const unassigneds = list_of_unassigned(locals);\n return env => bfun(extend_environment(locals, unassigneds, env));\n}\n```\n\nFor return statements, we analyze the return expression.\n\nThe execution function for the return statement simply calls\nthe execution function for the return expression and wraps\nthe result in a return value.\n\n```javascript\nanalyze_return_statement_example\n\t analyze\n\nanalyze_return_statement(list_ref(parse(\"() => x + 1;\"), 2))\n (extend_environment(list(\"x\"), list(6), the_global_environment));\n```", + "token_count": 295, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2860,8 +4110,8 @@ "chunk_id": "Metalinguistic_Abstraction_Separating_Syntactic_Analysis_from_Execution_3" }, { - "content": "must defer actually setting the variable until the execution, when the\nenvironment has been supplied.\n\nHowever, the fact that the\nassignment-value expression\ncan be analyzed (recursively) during analysis is a major gain in\nefficiency, because the\nassignment-value expression\nwill now be analyzed only once.\n\nThe same holds true for\nconstant and variable declarations.\n\n```javascript\nanalyze_assignment_example\n analyze\n\nanalyze_assignment(parse(\"x = x + 1;\"))\n (extend_environment(list(\"x\"), list(7), the_global_environment));\n```\n\n```javascript\nanalyze_assignment\n analyze_assignment_example\n 8\n\nfunction analyze_assignment(component) {\n const symbol = assignment_symbol(component);\n const vfun = analyze(assignment_value_expression(component));\n return env => {\n const value = vfun(env);\n assign_symbol_value(symbol, value, env);\n return value;\n };\n}\nfunction analyze_declaration(component) {\n const symbol = declaration_symbol(component);\n const vfun = analyze(declaration_value_expression(component));\n return env => {\n assign_symbol_value(symbol, vfun(env), env);\n return undefined;\n };\n}\n```\n\nOur new evaluator uses the same data structures, syntax functions, and runtime support functions as in sections , , and.", - "token_count": 138, + "content": "The execution function for the return statement simply calls\nthe execution function for the return expression and wraps\nthe result in a return value.\n\nThe function analyze_assignment\nmust defer actually setting the variable until the execution, when the\nenvironment has been supplied.\n\nHowever, the fact that the\nassignment-value expression\ncan be analyzed (recursively) during analysis is a major gain in\nefficiency, because the\nassignment-value expression\nwill now be analyzed only once.\n\nThe same holds true for\nconstant and variable declarations.\n\n```javascript\nanalyze_assignment_example\n analyze\n\nanalyze_assignment(parse(\"x = x + 1;\"))\n (extend_environment(list(\"x\"), list(7), the_global_environment));\n```\n\n```javascript\nanalyze_assignment\n analyze_assignment_example\n 8\n\nfunction analyze_assignment(component) {\n const symbol = assignment_symbol(component);\n const vfun = analyze(assignment_value_expression(component));\n return env => {\n const value = vfun(env);\n assign_symbol_value(symbol, value, env);\n return value;\n };\n}\nfunction analyze_declaration(component) {\n const symbol = declaration_symbol(component);\n const vfun = analyze(declaration_value_expression(component));\n return env => {\n assign_symbol_value(symbol, vfun(env), env);\n return undefined;\n };\n}\n```\n\nOur new evaluator uses the same data structures, syntax functions, and runtime support functions as in sections , , and.", + "token_count": 165, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2870,9 +4120,9 @@ "chunk_id": "Metalinguistic_Abstraction_Separating_Syntactic_Analysis_from_Execution_4" }, { - "content": "The The evaluation functions: evaluate and\n\nThe function evaluate takes as arguments\n\n```javascript\na program componenta statement or\n\texpressionand an environment.\n```\n\nIt classifies the\ncomponent\nand directs its evaluation.\n\nThe function evaluate\nis structured as a case analysis of the syntactic type of the\ncomponent\nto be evaluated.\n\nIn order to keep the\nfunction\ngeneral, we express\nthe determination of the type of\na component\nabstractly, making no\ncommitment to any particular\ncomponents.\n\nEach type of\ncomponent\nhas a\nsyntax predicate\nthat tests for it and an abstract means for selecting its parts.\n\nThis\nabstract syntax\nmakes it easy to see how we can change the syntax of the language by\nusing the same evaluator, but with a different collection of syntax\nfunctions.", - "token_count": 124, - "has_code": true, + "content": "The cycle exposes the essence of a computer language.\n\nThe evaluation\nprocess can be described as the interplay between two\nfunctions:\nevaluate\nand.\n\nThe function evaluate\ntakes as arguments\na program componenta statement or expressionand an environment.\n\nIt classifies the\ncomponent\nand directs its evaluation.\n\nThe function evaluate\nis structured as a case analysis of the syntactic type of the\ncomponent\nto be evaluated.\n\nIn order to keep the\nfunction\ngeneral, we express\nthe determination of the type of\na component\nabstractly, making no\ncommitment to any particular\nrepresentation for the various types of\ncomponents.\n\nEach type of\ncomponent\nhas a\nsyntax predicate\nthat tests for it and an abstract means for selecting its parts.\n\nThis\nabstract syntax\nmakes it easy to see how we can change the syntax of the language by\nusing the same evaluator, but with a different collection of syntax\nfunctions.\n\nFor literal expressions, such as numbers, evaluate returns their value.\n\nThe function evaluate must look up names in the environment to find their values.\n\nCombinations For a function application, evaluate must recursively evaluate the function expression and the argument expressions of the application.\n\nThe resulting function and arguments are passed to , which handles the actual function application.\n\nAn operator combination is transformed into a function application and then evaluated.\n\nSyntactic forms A conditional expression or statement requires special processing of its parts, so as to evaluate the consequent if the predicate is true, and otherwise to evaluate the alternative.\n\nA lambda expression must be transformed into an applicable function by packaging together the parameters and body specified by the lambda expression with the environment of the evaluation.\n\nA sequence of statements requires evaluating its components in the order in which they appear.", + "token_count": 288, + "has_code": false, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", "subsection": "The Core of the Evaluator", @@ -2880,9 +4130,9 @@ "chunk_id": "Metalinguistic_Abstraction_The_Core_of_the_Evaluator_1" }, { - "content": "This\nabstract syntax\nmakes it easy to see how we can change the syntax of the language by\nusing the same evaluator, but with a different collection of syntax\nfunctions.\n\n```javascript\nFor\n\t evaluate\n\t returns their value.\n\n\t The function\n\t evaluate\n\t must look up names in the environment to find their values.\n\n\t Combinations\n\n\t For a function application,\n\t evaluate must recursively\n\t evaluate the function expression and the argument expressions of the\n\t application. The resulting function and arguments are passed to\n\n\t An operator combination is transformed into a function application\n\t and then evaluated.\n\n\t Syntactic forms\n\n\t A conditional expression or statement requires special processing of\n\t its parts,\n\t so as to evaluate the consequent if the predicate is true, and\n\t otherwise to evaluate the alternative.\n\n\t A lambda expression must be transformed into an applicable\n\t function by packaging together the parameters and body specified\n\t by the lambda expression with the environment of the evaluation.\n\n\t A sequence of statements requires evaluating its\n\t components in the order in which they appear.\n\n\t A block requires evaluating its body in a new environment\n\t that reflects all names declared within the block.\n\n\t A return statement must produce a value that becomes the\n\t result of the function call that gave rise to the\n\t evaluation of the return statement.\n\n\t A function declaration is transformed into a\n\t constant declaration and then evaluated.\n\n\t A constant or variable declaration or an assignment must\n\t call\n\t evaluate\n\t recursively to compute the new\n\t value to be associated with the name being declared or assigned.\n\t The environment must be\n\t modified to reflect the new value of the name.\n\n\tHere is the declaration of\n\tevaluate:\n\n\t headline_4_1_1\n\n// functions from SICP JS 4.1.1\n\n\t eval\n\t eval_example\n\t 3\n\nfunction evaluate(component, env) {\n return is_literal(component)\n ? literal_value(component)\n : is_name(component)\n ? lookup_symbol_value(symbol_of_name(component), env)\n : is_application(component)\n ? apply(evaluate(function_expression(component), env),\n list_of_values(arg_expressions(component), env))\n : is_operator_combination(component)\n ? evaluate(operator_combination_to_application(component),\n env)\n : is_conditional(component)\n ? eval_conditional(component, env)\n : is_lambda_expression(component)\n ? make_function(lambda_parameter_symbols(component),\n lambda_body(component), env)\n : is_sequence(component)\n ? eval_sequence(sequence_statements(component), env)\n : is_block(component)\n ? eval_block(component, env)\n : is_return_statement(component)\n ? eval_return_statement(component, env)\n : is_function_declaration(component)\n ? evaluate(function_decl_to_constant_decl(component), env)\n : is_declaration(component)\n ? eval_declaration(component, env)\n : is_assignment(component)\n ? eval_assignment(component, env)\n : error(component, \"unknown syntax -- evaluate\");\n}\n\n\t eval_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nconst my_program = parse(\"1; { true; 3; }\");\nevaluate(my_program, the_empty_environment);\n```", - "token_count": 362, - "has_code": true, + "content": "A sequence of statements requires evaluating its components in the order in which they appear.\n\nA return statement must produce a value that becomes the result of the function call that gave rise to the evaluation of the return statement.\n\nA function declaration is transformed into a constant declaration and then evaluated.\n\nA constant or variable declaration or an assignment must call evaluate recursively to compute the new value to be associated with the name being declared or assigned.\n\nThe environment must be modified to reflect the new value of the name.\n\nHere is the declaration of evaluate: headline_4_1_1 // functions from SICP JS 4.1.1 eval eval_example 3 function evaluate(component, env) { return is_literal(component) ? literal_value(component) : is_name(component) ? lookup_symbol_value(symbol_of_name(component), env) : is_application(component) ? apply(evaluate(function_expression(component), env), list_of_values(arg_expressions(component), env)) : is_operator_combination(component) ? evaluate(operator_combination_to_application(component), env) : is_conditional(component) ? eval_conditional(component, env) : is_lambda_expression(component) ? make_function(lambda_parameter_symbols(component), lambda_body(component), env) : is_sequence(component) ? eval_sequence(sequence_statements(component), env) : is_block(component) ? eval_block(component, env) : is_return_statement(component) ? eval_return_statement(component, env) : is_function_declaration(component) ? evaluate(function_decl_to_constant_decl(component), env) : is_declaration(component) ? eval_declaration(component, env) : is_assignment(component) ? eval_assignment(component, env) : error(component, \"unknown syntax -- evaluate\"); } eval_example functions_4_1_1 functions_4_1_2 functions_4_1_3 functions_4_1_4 const my_program = parse(\"1; { true; 3; }\"); evaluate(my_program, the_empty_environment);\n\nFor clarity,\nevaluate\nhas been implemented as a\ncase analysis using\nconditional expressions.\n\nThe disadvantage of this is that our\nfunction\nhandles only a few distinguishable types of\nstatements and\nexpressions, and no new ones can be defined without editing the\ndeclaration of evaluate.\n\nIn most\ninterpreter\nimplementations, dispatching on the type of\na component\nis done in a data-directed style.\n\nThis allows a user to add new types of\ncomponents that evaluate\ncan distinguish, without modifying the\ndeclaration of evaluate\nitself.\n\n(See exercise.)\n\nThe representation of names is handled by the syntax abstractions.", + "token_count": 290, + "has_code": false, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", "subsection": "The Core of the Evaluator", @@ -2890,8 +4140,8 @@ "chunk_id": "Metalinguistic_Abstraction_The_Core_of_the_Evaluator_2" }, { - "content": "This\nabstract syntax\nmakes it easy to see how we can change the syntax of the language by\nusing the same evaluator, but with a different collection of syntax\nfunctions.\n\nFor clarity,\nevaluate\nhas been implemented as a\nconditional expressions.\n\nThe disadvantage of this is that our\nfunction\nhandles only a few distinguishable types of\nstatements and\nexpressions, and no new ones can be defined without editing the\n\n```javascript\ndeclaration of\n\tevaluate.\n```\n\nIn most\ninterpreter\nimplementations, dispatching on the type of\na component\nis done in a data-directed style.\n\nThis allows a user to add new types of\n\n```javascript\ncomponents that\n evaluate\n```\n\ncan distinguish, without modifying the\n\n```javascript\ndeclaration of\n\tevaluate\n```\n\nitself.\n\n(See exercise.)\n\nThe representation of names is handled by the syntax abstractions.\n\nInternally,\nthe evaluator uses strings to represent names, and we refer to such strings as\nsymbols.\n\nThe function\nsymbol_of_name used in\nevaluate extracts from a\nname the symbol by which it is represented.\n\nThe function apply\ntakes two arguments, a\nfunction\nand a list of arguments to which the\nfunction\nshould be applied.\n\nThe function apply\nclassifies\nfunctions\ninto two kinds: It calls\napply_primitive_function\nto apply primitives; it applies compound\nfunctions\n\n```javascript\nby evaluating the block that makes up the body\n\tof the function.\n```\n\nThe environment for the evaluation of the body of a compound\nfunction\nis constructed by extending the base environment carried by the\nfunction\nto include a frame that binds the parameters of the\nfunction\nto the arguments to which the\nfunction\nis to be applied.\n\nHere is the\ndeclaration\nof\n\n```javascript\napply\n apply_example\n 3\n\nfunction apply(fun, args) {\n if (is_primitive_function(fun)) {\n return apply_primitive_function(fun, args);\n } else if (is_compound_function(fun)) {\n const result = evaluate(function_body(fun),\n extend_environment(\n function_parameters(fun),\n args,\n function_environment(fun)));\n return is_return_value(result)\n ? return_value_content(result)\n : undefined;\n } else {\n error(fun, \"unknown function type -- apply\");\n }\n}\n```", - "token_count": 307, + "content": "The representation of names is handled by the syntax abstractions.\n\nThe function\nsymbol_of_name used in\nevaluate extracts from a\nname the symbol by which it is represented.\n\nThe function apply\ntakes two arguments, a\nfunction\nand a list of arguments to which the\nfunction\nshould be applied.\n\nThe function apply\nclassifies\nfunctions\ninto two kinds: It calls\napply_primitive_function\nto apply primitives; it applies compound\nfunctions\nby evaluating the block that makes up the body of the function.\n\nThe environment for the evaluation of the body of a compound\nfunction\nis constructed by extending the base environment carried by the\nfunction\nto include a frame that binds the parameters of the\nfunction\nto the arguments to which the\nfunction\nis to be applied.\n\nHere is the\ndeclaration\nof :\n\n```javascript\napply\n apply_example\n 3\n\nfunction apply(fun, args) {\n if (is_primitive_function(fun)) {\n return apply_primitive_function(fun, args);\n } else if (is_compound_function(fun)) {\n const result = evaluate(function_body(fun),\n extend_environment(\n function_parameters(fun),\n args,\n function_environment(fun)));\n return is_return_value(result)\n ? return_value_content(result)\n : undefined;\n } else {\n error(fun, \"unknown function type -- apply\");\n }\n}\n```\n\n```javascript\napply_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst plus = list(\"primitive\", (x, y) => x + y);\napply(plus, list(1, 2));\n```\n\nIn order to return a value, a JavaScript function needs to evaluate a return statement.\n\nIf a function terminates without evaluating a return statement, the value undefined is returned.\n\nTo distinguish the two cases, the evaluation of a return statement will wrap the result of evaluating its return expression into a return value.\n\nIf the evaluation of the function body yields such a return value, the content of the return value is retrieved; otherwise the value undefined is returned.\n\nWhen\nprocesses a\nfunction\napplication, it uses\nlist_of_values\nto produce the list of arguments to which the\nfunction\nis to be applied.", + "token_count": 294, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2900,8 +4150,8 @@ "chunk_id": "Metalinguistic_Abstraction_The_Core_of_the_Evaluator_3" }, { - "content": "Here is the\ndeclaration\nof\n\n```javascript\napply_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst plus = list(\"primitive\", (x, y) => x + y);\napply(plus, list(1, 2));\n```\n\n```javascript\nIn order to return a value, a JavaScript function needs to evaluate a\n\tundefined is returned.\n\tTo distinguish the two cases, the evaluation of a return statement\n\twill wrap the result of evaluating its return expression into a\n\treturn value. If\n\tthe evaluation of the function body yields such a return value, the content\n\tof the return value is retrieved; otherwise the value\n\tundefined is returned.\n```\n\nWhen processes a function application, it uses list_of_values to produce the list of arguments to which the function is to be applied.\n\n```javascript\nThe function\n list_of_values\n```\n\ntakes as an argument the\nargument expressions of the application.\n\nIt evaluates each\nargument expression\nand returns a\nlist of the corresponding values:\n\n```javascript\nlist_of_values\n list_of_values_example\n\nfunction list_of_values(exps, env) {\n return map(arg => evaluate(arg, env), exps);\n}\n```\n\n```javascript\nlist_of_values_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_addition_expression = parse(\"1 + 2;\");\nlist_of_values(list(parse(\"1;\"), my_addition_expression, parse(\"7;\")),\n the_global_environment);\n```\n\n```javascript\nThe function\n eval_conditional\n```\n\nevaluates the predicate part of\na conditional component\nin the given environment.\n\nIf the result is true,\nthe consequent is evaluated, otherwise the alternative is evaluated:\n\n```javascript\neval_if\n eval_if_example\n 1\n\nfunction eval_conditional(component, env) {\n return is_truthy(evaluate(conditional_predicate(component), env))\n ? evaluate(conditional_consequent(component), env)\n : evaluate(conditional_alternative(component), env);\n}\n```\n\n```javascript\neval_if_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_cond_expr = parse(\"true ? 1 : 2;\");\neval_conditional(my_cond_expr, the_empty_environment);\n```\n\n```javascript\nNote that the evaluator does not need to distinguish between\n\tconditional expressions and conditional statements.\n```\n\nThe use of\nis_truthy\nin\neval_conditional\nconditional_predicate\nis evaluated in the language being implemented and thus yields a value in\nthat language.", - "token_count": 281, + "content": "When\nprocesses a\nfunction\napplication, it uses\nlist_of_values\nto produce the list of arguments to which the\nfunction\nis to be applied.\n\nIt evaluates each\nargument expression\nand returns a\nlist of the corresponding values:\n\n```javascript\nlist_of_values\n list_of_values_example\n\nfunction list_of_values(exps, env) {\n return map(arg => evaluate(arg, env), exps);\n}\n```\n\n```javascript\nlist_of_values_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_addition_expression = parse(\"1 + 2;\");\nlist_of_values(list(parse(\"1;\"), my_addition_expression, parse(\"7;\")),\n the_global_environment);\n```\n\nThe function eval_conditional\nevaluates the predicate part of\na conditional component\nin the given environment.\n\nIf the result is true,\nthe consequent is evaluated, otherwise the alternative is evaluated:\n\n```javascript\neval_if\n eval_if_example\n 1\n\nfunction eval_conditional(component, env) {\n return is_truthy(evaluate(conditional_predicate(component), env))\n ? evaluate(conditional_consequent(component), env)\n : evaluate(conditional_alternative(component), env);\n}\n```\n\n```javascript\neval_if_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_cond_expr = parse(\"true ? 1 : 2;\");\neval_conditional(my_cond_expr, the_empty_environment);\n```\n\nNote that the evaluator does not need to distinguish between conditional expressions and conditional statements.\n\nThe use of\nis_truthy\nin\neval_conditional\nhighlights the issue of the connection between an implemented language and\nan implementation language.\n\nThe\nconditional_predicate\nis evaluated in the language being implemented and thus yields a value in\nthat language.\n\nThe interpreter predicate\nis_truthy\ntranslates that value into a value that can be tested by the\nconditional expression\nin the implementation language: The metacircular representation of truth\nmight not be the same as that of the underlying\nJavaScript.\n\nThe function eval_sequence\nis used by evaluate\nto evaluate a sequence of statements at the top level or in a block.\n\nIt takes as arguments a sequence of statements and an\nenvironment, and evaluates the statements in the order in which they\noccur.\n\nThe value returned is the value of the final statement, except\nthat if the evaluation of any statement in the sequence yields\na return value, that value is returned and the subsequent statements are\nignored.", + "token_count": 300, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2910,8 +4160,8 @@ "chunk_id": "Metalinguistic_Abstraction_The_Core_of_the_Evaluator_4" }, { - "content": "The use of\nis_truthy\nin\neval_conditional\nconditional_predicate\nis evaluated in the language being implemented and thus yields a value in\nthat language.\n\nThe interpreter predicate\nis_truthy\ntranslates that value into a value that can be tested by the\nconditional expression\nin the implementation language: The metacircular representation of truth\nmight not be the same as that of the underlying\nJavaScript.\n\nThe function eval_sequence\nis used by evaluate\nto evaluate a sequence of statements at the top level or in a block.\n\nIt takes as arguments a sequence of statements and an\nenvironment, and evaluates the statements in the order in which they\noccur.\n\nThe value returned is the value of the final statement, except\nthat if the evaluation of any statement in the sequence yields\na return value, that value is returned and the subsequent statements are\nignored.\n\n```javascript\neval_sequence\n\t eval_sequence_example\n\t 3\n\nfunction eval_sequence(stmts, env) {\n if (is_empty_sequence(stmts)) {\n return undefined;\n } else if (is_last_statement(stmts)) {\n return evaluate(first_statement(stmts), env);\n } else {\n const first_stmt_value =\n evaluate(first_statement(stmts), env);\n if (is_return_value(first_stmt_value)) {\n return first_stmt_value;\n } else {\n return eval_sequence(rest_statements(stmts), env);\n }\n }\n}\n```\n\n```javascript\neval_sequence_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nconst my_sequence = head(tail(parse(\"1; true; 3;\")));\neval_sequence(my_sequence, the_empty_environment);\n```\n\nThe function eval_block handles\nblocks.\n\nThe variables and constants (including functions)\ndeclared in the block have the whole block as their scope and thus\nare scanned out before the body of the block is\nevaluated.\n\nThe body of the block is evaluated with respect to an environment\nthat extends the current\nenvironment by a frame that binds each local name\nto a special value,\n\"*unassigned*\".\n\nThis string serves as a placeholder, before\nthe evaluation of the declaration assigns the name\nits proper value.", - "token_count": 280, + "content": "The value returned is the value of the final statement, except\nthat if the evaluation of any statement in the sequence yields\na return value, that value is returned and the subsequent statements are\nignored.\n\n```javascript\neval_sequence_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nconst my_sequence = head(tail(parse(\"1; true; 3;\")));\neval_sequence(my_sequence, the_empty_environment);\n```\n\nThe function eval_block handles\nblocks.\n\nThe variables and constants (including functions)\ndeclared in the block have the whole block as their scope and thus\nare scanned out before the body of the block is\nevaluated.\n\nThe body of the block is evaluated with respect to an environment\nthat extends the current\nenvironment by a frame that binds each local name\nto a special value,\n\"*unassigned*\".\n\nThis string serves as a placeholder, before\nthe evaluation of the declaration assigns the name\nits proper value.\n\nAn attempt to access the value of the name before its\ndeclaration is evaluated leads to an error at run time (see\nexercise ), as stated in\nfootnote in chapter.\n\n```javascript\neval_block\n scan_out_declarations\n\t eval_block_example\n\t 42\n\nfunction eval_block(component, env) {\n const body = block_body(component);\n const locals = scan_out_declarations(body);\n const unassigneds = list_of_unassigned(locals);\n return evaluate(body, extend_environment(locals,\n unassigneds,\n env));\n}\nfunction list_of_unassigned(symbols) {\n return map(symbol => \"*unassigned*\", symbols);\n}\n```\n\n```javascript\nlist_of_unassigned\n\nfunction list_of_unassigned(symbols) {\n return map(symbol => \"*unassigned*\", symbols);\n}\n```\n\nThe function scan_out_declarations\ncollects a list of all symbols representing names declared in the body.\n\nIt uses\ndeclaration_symbol\nto retrieve the symbol that represents the name\nfrom the declaration statements it finds.\n\n```javascript\nscan_out_declarations\n\t scan_out_declarations_example\n\t [ 'x', [ 'y', null ] ]\n\nfunction scan_out_declarations(component) {\n return is_sequence(component)\n ? accumulate(append,\n null,\n map(scan_out_declarations,\n sequence_statements(component)))\n : is_declaration(component)\n ? list(declaration_symbol(component))\n : null;\n}\n```\n\n```javascript\nscan_out_declarations_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nscan_out_declarations(parse(\"const x = 1; let y = 2;\"));\n```\n\n```javascript\neval_block_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nconst my_block = parse(\"{ const x = 1; 3; 42; }\");\neval_block(my_block, the_empty_environment);\n```", + "token_count": 307, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2920,8 +4170,8 @@ "chunk_id": "Metalinguistic_Abstraction_The_Core_of_the_Evaluator_5" }, { - "content": "This string serves as a placeholder, before\nthe evaluation of the declaration assigns the name\nits proper value.\n\nAn attempt to access the value of the name before its\ndeclaration is evaluated leads to an error at run time (see\nexercise ), as stated in\nfootnote in chapter.\n\n```javascript\neval_block\n scan_out_declarations\n\t eval_block_example\n\t 42\n\nfunction eval_block(component, env) {\n const body = block_body(component);\n const locals = scan_out_declarations(body);\n const unassigneds = list_of_unassigned(locals);\n return evaluate(body, extend_environment(locals,\n unassigneds,\n env));\n}\nfunction list_of_unassigned(symbols) {\n return map(symbol => \"*unassigned*\", symbols);\n}\n```\n\n```javascript\nlist_of_unassigned\n\nfunction list_of_unassigned(symbols) {\n return map(symbol => \"*unassigned*\", symbols);\n}\n```\n\nThe function scan_out_declarations declaration_symbol to retrieve the symbol that represents the name from the declaration statements it finds.\n\n```javascript\nscan_out_declarations\n\t scan_out_declarations_example\n\t [ 'x', [ 'y', null ] ]\n\nfunction scan_out_declarations(component) {\n return is_sequence(component)\n ? accumulate(append,\n null,\n map(scan_out_declarations,\n sequence_statements(component)))\n : is_declaration(component)\n ? list(declaration_symbol(component))\n : null;\n}\n```\n\n```javascript\nscan_out_declarations_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nscan_out_declarations(parse(\"const x = 1; let y = 2;\"));\n```\n\n```javascript\neval_block_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nconst my_block = parse(\"{ const x = 1; 3; 42; }\");\neval_block(my_block, the_empty_environment);\n```\n\nWe ignore declarations that are nested in another block,\nbecause the evaluation of that block will take care of them.\n\nThe function scan_out_declarations\nlooks for declarations only in sequences because\ndeclarations in conditional statements, function declarations, and\nlambda expressions are always in a nested block.\n\nThe function eval_return_statement\nis used to evaluate\napply and\nthe evaluation\nof sequences, the result of evaluation of a return statement\nneeds to be identifiable so that the evaluation of a function\nbody can return immediately, even if there are statements\nafter the return statement.\n\nFor this purpose,\nthe evaluation of a return statement wraps the result of\nevaluating the return expression in a return value object.\n\n```javascript\neval_return\n\t eval_return_example\n\t 1\n\nfunction eval_return_statement(component, env) {\n return make_return_value(evaluate(return_expression(component),\n env));\n}\n```", - "token_count": 303, + "content": "It uses\ndeclaration_symbol\nto retrieve the symbol that represents the name\nfrom the declaration statements it finds.\n\nThe function scan_out_declarations\nlooks for declarations only in sequences because\ndeclarations in conditional statements, function declarations, and\nlambda expressions are always in a nested block.\n\nThe function eval_return_statement\nis used to evaluate\nreturn statements.\n\nAs seen in\napply and\nthe evaluation\nof sequences, the result of evaluation of a return statement\nneeds to be identifiable so that the evaluation of a function\nbody can return immediately, even if there are statements\nafter the return statement.\n\nFor this purpose,\nthe evaluation of a return statement wraps the result of\nevaluating the return expression in a return value object.\n\n```javascript\neval_return\n\t eval_return_example\n\t 1\n\nfunction eval_return_statement(component, env) {\n return make_return_value(evaluate(return_expression(component),\n env));\n}\n```\n\n```javascript\neval_return_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nconst my_program = parse(\"{ function f() { return 1; } f(); }\");\nevaluate(my_program, the_global_environment);\n```\n\nThe\nfunction eval_assignment\nhandles assignments to\nnames.\n\n(To simplify the presentation of our evaluator, we are allowing assignment not just to variables but alsoerroneouslyto constants.\n\nExercise explains how we could distinguish constants from variables and prevent assignment to constants.)\nThe function eval_assignment calls on the value expression to find the value to be assigned and calls assignment_symbol to retrieve the symbol that represents the name from the assignment.\n\nThe function eval_assignment transmits the symbol and the value to assign_symbol_value to be installed in the designated environment.\n\nThe evaluation of an assignment returns the value that was assigned.\n\n```javascript\neval_assignment\n eval_assignment_example\n 2\n\nfunction eval_assignment(component, env) {\n const value = evaluate(assignment_value_expression(component),\n env);\n assign_symbol_value(assignment_symbol(component), value, env);\n return value;\n}\n```\n\n```javascript\neval_assignment_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_program = parse(\"{ let x = 1; x = 2; }\");\nevaluate(my_program, the_global_environment);\n```\n\nConstant and variable declarations are both recognized by the\nis_declaration syntax predicate.", + "token_count": 298, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2930,8 +4180,8 @@ "chunk_id": "Metalinguistic_Abstraction_The_Core_of_the_Evaluator_6" }, { - "content": "For this purpose,\nthe evaluation of a return statement wraps the result of\nevaluating the return expression in a return value object.\n\n```javascript\neval_return_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nconst my_program = parse(\"{ function f() { return 1; } f(); }\");\nevaluate(my_program, the_global_environment);\n```\n\nThe function eval_assignment handles assignments to\n\n```javascript\nnames.\n\t(To simplify the\tpresentation of our evaluator,\n\twe are allowing assignment not just to variables but\n\talsoerroneouslyto constants.\n\tExercise\n\texplains how we could\n\tdistinguish constants from variables and prevent\n\tassignment to constants.)\n```\n\n```javascript\nThe function\n\teval_assignment\n\tcalls\n\tassignment_symbol\n\tto retrieve the symbol that represents the name\n\tfrom the assignment. The function\n\teval_assignment\n\ttransmits the symbol and the value to\n\tassign_symbol_value\n\tto be installed in the designated environment.\n\tThe evaluation of an assignment returns the value\n\tthat was assigned.\n```\n\n```javascript\neval_assignment\n eval_assignment_example\n 2\n\nfunction eval_assignment(component, env) {\n const value = evaluate(assignment_value_expression(component),\n env);\n assign_symbol_value(assignment_symbol(component), value, env);\n return value;\n}\n```\n\n```javascript\neval_assignment_example\n functions_4_1_1\n functions_4_1_2\n functions_4_1_3\n functions_4_1_4\n\nconst my_program = parse(\"{ let x = 1; x = 2; }\");\nevaluate(my_program, the_global_environment);\n```\n\nConstant and variable declarations are both recognized by the\nis_declaration syntax predicate.\n\nThey are treated in a manner similar to\nassignments, because eval_block\nhas already bound their symbols to \"*unassigned*\"\nin the current environment.\n\nTheir evaluation replaces \"*unassigned*\"\nwith the result of evaluating the value expression.\n\n```javascript\neval_definition\n\t eval_definition_example\n\t 3\n\nfunction eval_declaration(component, env) {\n assign_symbol_value(\n declaration_symbol(component),\n evaluate(declaration_value_expression(component), env),\n env);\n return undefined;\n}\n```\n\nThe result of evaluating the body of a function is determined by\nreturn statements, and therefore the return value\nundefined in\neval_declaration only\nmatters when the declaration occurs at the top level,\noutside of any function body.\n\nHere we use the return value\nundefined to simplify\nthe presentation; exercise\ndescribes the real result of evaluating top-level components\nin JavaScript.", - "token_count": 294, + "content": "Constant and variable declarations are both recognized by the\nis_declaration syntax predicate.\n\nTheir evaluation replaces \"*unassigned*\"\nwith the result of evaluating the value expression.\n\n```javascript\neval_definition\n\t eval_definition_example\n\t 3\n\nfunction eval_declaration(component, env) {\n assign_symbol_value(\n declaration_symbol(component),\n evaluate(declaration_value_expression(component), env),\n env);\n return undefined;\n}\n```\n\nThe result of evaluating the body of a function is determined by\nreturn statements, and therefore the return value\nundefined in\neval_declaration only\nmatters when the declaration occurs at the top level,\noutside of any function body.\n\nHere we use the return value\nundefined to simplify\nthe presentation; exercise\ndescribes the real result of evaluating top-level components\nin JavaScript.\n\n```javascript\neval_definition_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nconst my_program = parse(\"{ let x = 1; const y = 2; x + y; }\");\nevaluate(my_program, the_global_environment);\n```", + "token_count": 125, "has_code": true, "chapter": "Metalinguistic Abstraction", "section": "The Metacircular Evaluator", @@ -2940,18 +4190,8 @@ "chunk_id": "Metalinguistic_Abstraction_The_Core_of_the_Evaluator_7" }, { - "content": "Here we use the return value\nundefined to simplify\nthe presentation; exercise\ndescribes the real result of evaluating top-level components\nin JavaScript.\n\n```javascript\neval_definition_example\n\t functions_4_1_1\n\t functions_4_1_2\n\t functions_4_1_3\n\t functions_4_1_4\n\nconst my_program = parse(\"{ let x = 1; const y = 2; x + y; }\");\nevaluate(my_program, the_global_environment);\n```", - "token_count": 47, - "has_code": true, - "chapter": "Metalinguistic Abstraction", - "section": "The Metacircular Evaluator", - "subsection": "The Core of the Evaluator", - "chunk_index": 8, - "chunk_id": "Metalinguistic_Abstraction_The_Core_of_the_Evaluator_8" - }, - { - "content": "The preceding chapters introduced the basic elements from which\nprograms are made.\n\nWe saw how primitive\nfunctions\nand primitive data are combined to construct compound entities, and we\nlearned that abstraction is vital in helping us to cope with the complexity\nof large systems.\n\nBut these tools are not sufficient for designing\nprograms.\n\nEffective program synthesis also requires organizational\nprinciples that can guide us in formulating the overall design of a\nprogram.\n\nIn particular, we need strategies to help us structure large\nsystems so that they will be\nmodular , that is, so that they can\nbe divided naturally into coherent parts that can be\nseparately developed and maintained.\n\nOne powerful design strategy, which is particularly appropriate to the construction of programs for\n\nTo a large extent, then, the way we organize a large program is\ndictated by our perception of the system to be modeled.\n\nIn this\nchapter we will investigate two prominent organizational strategies\narising from two rather different world views of the\nstructure of systems.\n\nThe first organizational strategy concentrates on\nobjects , viewing a large system as a collection of distinct objects\nwhose behaviors may change over time.\n\nAn alternative organizational\nstrategy concentrates on the\nstreams of information that flow in\nthe system, much as an electrical engineer views a signal-processing\nsystem.\n\nBoth the object-based approach and the stream-processing approach\nraise significant linguistic issues in programming.\n\nWith objects, we must be concerned with how a computational object can\nchange and yet maintain its identity.\n\nThis will force us to abandon\nour old substitution model of computation\n(section ) in favor of a more\nmechanistic but less theoretically tractable\nenvironment model of\ncomputation.\n\nThe difficulties of dealing with objects, change, and\nidentity are a fundamental consequence of the need to grapple with\ntime in our computational models.", - "token_count": 300, + "content": "The preceding chapters introduced the basic elements from which\nprograms are made.\n\nWe saw how primitive\nfunctions\nand primitive data are combined to construct compound entities, and we\nlearned that abstraction is vital in helping us to cope with the complexity\nof large systems.\n\nBut these tools are not sufficient for designing\nprograms.\n\nEffective program synthesis also requires organizational\nprinciples that can guide us in formulating the overall design of a\nprogram.\n\nIn particular, we need strategies to help us structure large\nsystems so that they will be\nmodular , that is, so that they can\nbe divided naturally into coherent parts that can be\nseparately developed and maintained.\n\nOne powerful design strategy, which is particularly appropriate to the\nconstruction of programs for\nmodeling physical systems, is to base the\nstructure of our programs on the structure of the system being\nmodeled.\n\nFor each object in the system, we construct a corresponding\ncomputational object.\n\nFor each system action, we define a symbolic\noperation in our computational model.\n\nOur hope in using this strategy\nis that extending the model to accommodate new objects or new actions\nwill require no strategic changes to the program, only the addition of\nthe new symbolic analogs of those objects or actions.\n\nIf we have been\nsuccessful in our system organization, then to add a new feature or\ndebug an old one we will have to work on only a localized part of the\nsystem.\n\nTo a large extent, then, the way we organize a large program is\ndictated by our perception of the system to be modeled.\n\nIn this\nchapter we will investigate two prominent organizational strategies\narising from two rather different world views of the\nstructure of systems.", + "token_count": 284, "has_code": false, "chapter": "Modularity, Objects, and State", "section": null, @@ -2960,8 +4200,8 @@ "chunk_id": "Modularity_Objects_and_State_Modularity_Objects_and_State_1" }, { - "content": "The difficulties of dealing with objects, change, and\nidentity are a fundamental consequence of the need to grapple with\ntime in our computational models.\n\nThese difficulties become even\ngreater when we allow the possibility of concurrent execution of\nprograms.\n\nThe stream approach can be most fully exploited when we\ndecouple simulated time in our model from the order of the events that\ntake place in the computer during evaluation.\n\nWe will accomplish this\nusing a technique known as\ndelayed evaluation.", - "token_count": 80, + "content": "In this\nchapter we will investigate two prominent organizational strategies\narising from two rather different world views of the\nstructure of systems.\n\nAn alternative organizational\nstrategy concentrates on the\nstreams of information that flow in\nthe system, much as an electrical engineer views a signal-processing\nsystem.\n\nBoth the object-based approach and the stream-processing approach\nraise significant linguistic issues in programming.\n\nWith objects, we must be concerned with how a computational object can\nchange and yet maintain its identity.\n\nThis will force us to abandon\nour old substitution model of computation\n(section ) in favor of a more\nmechanistic but less theoretically tractable\nenvironment model of\ncomputation.\n\nThe difficulties of dealing with objects, change, and\nidentity are a fundamental consequence of the need to grapple with\ntime in our computational models.\n\nThese difficulties become even\ngreater when we allow the possibility of concurrent execution of\nprograms.\n\nThe stream approach can be most fully exploited when we\ndecouple simulated time in our model from the order of the events that\ntake place in the computer during evaluation.\n\nWe will accomplish this\nusing a technique known as\ndelayed evaluation.", + "token_count": 186, "has_code": false, "chapter": "Modularity, Objects, and State", "section": null, @@ -2980,8 +4220,8 @@ "chunk_id": "Modularity_Objects_and_State_Assignment_and_Local_State_1" }, { - "content": "Since the states of objects in the system being modeled change over\ntime, the state variables of the corresponding computational objects\nmust also change.\n\nIf we choose to model the flow of time in the\nsystem by the elapsed time in the computer, then we must have a way to\nconstruct computational objects whose behaviors change as our programs\nrun.\n\nIn particular, if we wish to model state variables by ordinary\nsymbolic names in the programming language, then the language must\nprovide an\nassignment operation\nto enable us to change the value\nassociated with a name.", - "token_count": 96, + "content": "Since the states of objects in the system being modeled change over\ntime, the state variables of the corresponding computational objects\nmust also change.\n\nIn particular, if we wish to model state variables by ordinary\nsymbolic names in the programming language, then the language must\nprovide an\nassignment operation\nto enable us to change the value\nassociated with a name.", + "token_count": 60, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Assignment and Local State", @@ -2990,8 +4230,8 @@ "chunk_id": "Modularity_Objects_and_State_Assignment_and_Local_State_2" }, { - "content": "To illustrate what we mean by having a computational object with\nfunction\nInsufficient funds.\n\nFor example, if we begin with 100\nin the account, we should obtain the following sequence of responses\nusing\n\n```javascript\nwithdraw_example\n 75\n withdraw\n\nwithdraw(25);\n```\n\n```javascript\nwithdraw_example2\n withdraw\n withdraw_example\n 50\n\nwithdraw(25);\n```\n\n```javascript\nwithdraw_example3\n withdraw\n withdraw_example\n withdraw_example2\n 'Insufficient funds'\n\nwithdraw(60);\n```\n\n```javascript\nwithdraw_example4\n withdraw\n withdraw_example\n withdraw_example2\n withdraw_example3\n 35\n\nwithdraw(15);\n```\n\nObserve that the expression\nwithdraw(25),\nevaluated twice, yields different values.\n\nThis is a new kind of\nbehavior for a\nfunction.\n\nUntil now, all our\nJavaScript functions\ncould be viewed as specifications for computing mathematical functions.\n\nA call to a\nfunction\ncomputed the value of the function applied to the given arguments,\nand two calls to the same\nfunction\nwith the same arguments always produced the same\nresult.\n\n```javascript\nSo far, all our names have been immutable.\n\tWhen a function was applied, the values that its parameters\n\treferred to never changed, and once a declaration was evaluated,\n\tthe declared name never changed its value.\n To implement functions like\n\tvariable declarations, which use the keyword\n\tlet, in addition to constant\n\tdeclarations, which use the keyword\n\tconst.\n We can declare a variable\n\tbalance\n\tto indicate the balance of money\n\tin the account and define\n```\n\nThe\nfunction\nchecks to see if Insufficient funds\nmessage.\n\nHere are the\ndeclarations\nof\n\n```javascript\nwithdraw\n withdraw_example\n\nlet balance = 100;\n\nfunction withdraw(amount) {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n}\n```\n\nDecrementing statement\n\n```javascript\nbalance = balance - amount;\n```\n\n```javascript\nThe syntax of\n\tassignment expressions is\n\nname = new-value\n```\n\nHere\n\n```javascript\nname\n\thas been declared with\n\tlet or\n\tas a\n```\n\nand\nnew-value\nis any expression.\n\nThe assignment\nchanges\nname\nso that its value is the\nresult obtained by evaluating\nnew-value.\n\nIn the case at hand, we are changing", - "token_count": 311, + "content": "To illustrate what we mean by having a computational object with\ntime-varying state, let us model the situation of withdrawing money\nfrom a\nbank account.\n\nWe will do this using a\nfunction\n, which takes as argument an\nto be withdrawn.\n\nIf there is enough money in the account to accommodate the withdrawal,\nthen should return the balance\nremaining after the withdrawal.\n\nOtherwise,\nshould return the message\nInsufficient funds.\n\nFor example, if we begin with 100\nin the account, we should obtain the following sequence of responses\nusing\n:\n\n```javascript\nwithdraw_example\n 75\n withdraw\n\nwithdraw(25);\n\n75\n```\n\n```javascript\nwithdraw_example2\n withdraw\n withdraw_example\n 50\n\nwithdraw(25);\n\n50\n```\n\n```javascript\nwithdraw_example3\n withdraw\n withdraw_example\n withdraw_example2\n 'Insufficient funds'\n\nwithdraw(60);\n\n\"Insufficient funds\"\n```\n\n```javascript\nwithdraw_example4\n withdraw\n withdraw_example\n withdraw_example2\n withdraw_example3\n 35\n\nwithdraw(15);\n\n35\n```\n\nObserve that the expression\nwithdraw(25),\nevaluated twice, yields different values.\n\nThis is a new kind of\nbehavior for a\nfunction.\n\nUntil now, all our\nJavaScript functions\ncould be viewed as specifications for computing mathematical functions.\n\nA call to a\nfunction\ncomputed the value of the function applied to the given arguments,\nand two calls to the same\nfunction\nwith the same arguments always produced the same\nresult.\n\nSo far, all our names have been immutable.\n\nWhen a function was applied, the values that its parameters referred to never changed, and once a declaration was evaluated, the declared name never changed its value.\n\nTo implement functions like , we introduce variable declarations, which use the keyword let, in addition to constant declarations, which use the keyword const.\n\nWe can declare a variable balance to indicate the balance of money in the account and define as a function that accesses.\n\nThe\nfunction\nchecks to see if is at least as large\nas the requested.\n\nIf so,\ndecrements\nby\nand returns the new value of.\n\nOtherwise,\nreturns the Insufficient funds\nmessage.", + "token_count": 303, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Assignment and Local State", @@ -3000,8 +4240,8 @@ "chunk_id": "Modularity_Objects_and_State_Local_State_Variables_1" }, { - "content": "In the case at hand, we are changing\n\n```javascript\nThe function withdraw also uses a\n\tsequence of statements to cause two statements to be evaluated\n\tin the case where the if test is\n\ttrue: first decrementing balance\n\tand then returning the value of\n\tbalance.\n\tIn general, executing a sequence\n\nstmt$_{1}$ stmt$_{2} \\ldots$stmt$_{n}$\n\n causes the statements stmt$_{1}$\n\tthrough\n\tstmt$_{n}$ to be evaluated in\n\tsequence.\n```\n\nAlthough\nprogram\nenvironment and is freely accessible to be examined or\nmodified by any\nfunction.\n\nIt would be much better if we could somehow make\nfunction\nthat could access\nfunction\ncould access\n\nWe can make\n\n```javascript\nnew_withdraw_example\n\nnew_withdraw(60);\nnew_withdraw(60);\n```\n\n```javascript\nnew_withdraw\n new_withdraw_example\n\nfunction make_withdraw_balance_100() {\n let balance = 100;\n return amount => {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n };\n}\nconst new_withdraw = make_withdraw_balance_100();\n```\n\n```javascript\nWhat we have done here is use let\n\tto establish an environment with a local variable\n\tbalance, bound to the initial\n\tvalue 100. Within this local environment, we use a lambda\n\texpressionamount\n\tas an argument and behaves like our previous\n\twithdraw function. This\n\tfunctionreturned as the result of evaluating the body of the\n\tmake_withdraw_balance_100\n\tfunctionbehaves in precisely the same way as\n\twithdraw, but its variable\n\tbalance is not accessible by any\n\tother function.\n```\n\nCombining\nassignments with variable declarations\nis the general programming\ntechnique we will use for constructing computational objects with\nlocal state.\n\nUnfortunately, using this technique raises a serious\nproblem: When we first introduced\nfunctions,\nwe also introduced the substitution model of evaluation\n(section ) to provide an\ninterpretation of what\nfunction\napplication means.\n\nWe said that applying a\nfunction whose body is a return statement\nshould be interpreted as evaluating the\nreturn expression of the function\nwith the\nparameters replaced by their values.", - "token_count": 301, + "content": "Otherwise,\nreturns the Insufficient funds\nmessage.\n\n```javascript\nwithdraw\n withdraw_example\n\nlet balance = 100;\n\nfunction withdraw(amount) {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n}\n```\n\nDecrementing is accomplished by the statement\n\n```javascript\nbalance = balance - amount;\n```\n\nThe syntax of assignment expressions is name = new-value\nHere\nname has been declared with let or as a function parameter\nand\nnew-value\nis any expression.\n\nThe assignment\nchanges\nname\nso that its value is the\nresult obtained by evaluating\nnew-value.\n\nIn the case at hand, we are changing so\nthat its new value will be the result of subtracting\nfrom the previous value of.\n\nThe function withdraw also uses a sequence of statements to cause two statements to be evaluated in the case where the if test is true: first decrementing balance and then returning the value of balance.\n\nIn general, executing a sequence stmt$_{1}$ stmt$_{2} \\ldots$stmt$_{n}$ causes the statements stmt$_{1}$ through stmt$_{n}$ to be evaluated in sequence.\n\nAlthough works as desired, the\nvariable presents a problem.\n\nAs\nspecified above, is a name defined\nin the\nprogram\nenvironment and is freely accessible to be examined or\nmodified by any\nfunction.\n\nIt would be much better if we could somehow make\ninternal to\n, so that\nwould be the only\nfunction\nthat could access directly and\nany other\nfunction\ncould access only indirectly\n(through calls to ).\n\nThis would\nmore accurately model the notion that\nis a local state variable used by\nto keep track of the state of the\naccount.\n\nWe can make internal to by rewriting the definition as follows:\n\n```javascript\nnew_withdraw_example\n\nnew_withdraw(60);\nnew_withdraw(60);\n```", + "token_count": 277, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Assignment and Local State", @@ -3010,9 +4250,9 @@ "chunk_id": "Modularity_Objects_and_State_Local_State_Variables_2" }, { - "content": "We said that applying a\nfunction whose body is a return statement\nshould be interpreted as evaluating the\nreturn expression of the function\nwith the\nparameters replaced by their values.\n\n```javascript\nFor functions with more complex\n\tbodies, we need to evaluate the whole body with the\n\tparameters replaced by their values.\n```\n\nThe trouble is that,\nas soon as we introduce assignment into our language, substitution is no\nlonger an adequate model of\nfunction\napplication.\n\n(We will see why this is so in\nsection.) As a consequence, we\ntechnically have at this point no way to understand why the\nnew_withdraw\nfunction\nbehaves as claimed above.\n\nIn order to really understand a\nfunction\nsuch as\nnew_withdraw,\nwe will need to develop a new model of\nfunction\napplication.\n\nIn section we will\nintroduce such a model, together with an explanation of\nassignments and variable declarations.\n\nFirst, however, we examine some variations on the theme established by\nnew_withdraw.\n\n```javascript\nParameters of functions as well as names declared with\n\tlet are\n```\n\nThe following\nfunction, make_withdraw,\ncreates withdrawal processors.\n\nThe parameter\nmake_withdraw\nspecifies the initial amount of money in the\naccount.\n\n```javascript\nmake_withdraw\n make_withdraw_define\n\nfunction make_withdraw(balance) {\n return amount => {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n };\n}\n```\n\nThe function make_withdraw can be used as follows to create two objects\n\n```javascript\nmake_withdraw_define\n make_withdraw\n\nconst W1 = make_withdraw(100);\nconst W2 = make_withdraw(100);\n```\n\n```javascript\nmake_withdraw_example1\n make_withdraw_define\n 50\n\nW1(50);\n```\n\n```javascript\nmake_withdraw_example1\n make_withdraw_example2\n 30\n\nW2(70);\n```\n\n```javascript\nmake_withdraw_example3\n make_withdraw_example2\n 'Insufficient funds'\n\nW2(40);\n```\n\n```javascript\nmake_withdraw_example4\n make_withdraw_example3\n 10\n\nW1(40);\n```\n\nObserve that\n\nWe can also create objects that handle function that returns a bank-account object with a specified initial balance:", - "token_count": 288, - "has_code": true, + "content": "We can make internal to by rewriting the definition as follows:\n\nWhat we have done here is use let to establish an environment with a local variable balance, bound to the initial value 100.\n\nWithin this local environment, we use a lambda expression to create a function that takes amount as an argument and behaves like our previous withdraw function.\n\nThis functionreturned as the result of evaluating the body of the make_withdraw_balance_100 functionbehaves in precisely the same way as withdraw, but its variable balance is not accessible by any other function.\n\nCombining\nassignments with variable declarations\nis the general programming\ntechnique we will use for constructing computational objects with\nlocal state.\n\nUnfortunately, using this technique raises a serious\nproblem: When we first introduced\nfunctions,\nwe also introduced the substitution model of evaluation\n(section ) to provide an\ninterpretation of what\nfunction\napplication means.\n\nWe said that applying a\nfunction whose body is a return statement\nshould be interpreted as evaluating the\nreturn expression of the function\nwith the\nparameters replaced by their values.\n\nFor functions with more complex bodies, we need to evaluate the whole body with the parameters replaced by their values.\n\nThe trouble is that,\nas soon as we introduce assignment into our language, substitution is no\nlonger an adequate model of\nfunction\napplication.\n\n(We will see why this is so in\nsection.) As a consequence, we\ntechnically have at this point no way to understand why the\nnew_withdraw\nfunction\nbehaves as claimed above.\n\nIn order to really understand a\nfunction\nsuch as\nnew_withdraw,\nwe will need to develop a new model of\nfunction\napplication.\n\nIn section we will\nintroduce such a model, together with an explanation of\nassignments and variable declarations.\n\nFirst, however, we examine some variations on the theme established by\nnew_withdraw.", + "token_count": 296, + "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Assignment and Local State", "subsection": "Local State Variables", @@ -3020,8 +4260,8 @@ "chunk_id": "Modularity_Objects_and_State_Local_State_Variables_3" }, { - "content": "We can also create objects that handle function that returns a bank-account object with a specified initial balance:\n\n```javascript\nmake_account\n make_account_example_my\n\nfunction make_account(balance) {\n function withdraw(amount) {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n }\n function deposit(amount) {\n balance = balance + amount;\n return balance;\n }\n function dispatch(m) {\n return m === \"withdraw\"\n ? withdraw\n : m === \"deposit\"\n ? deposit\n : error(m, \"unknown request -- make_account\");\n }\n return dispatch;\n}\n```\n\n```javascript\nmake_account_example_my\n\nconst acc = make_account(100);\n\nacc(\"withdraw\")(50);\n```\n\nEach call to\nfunctions\nfunction\nmessage as input and returns one of the two local\nfunctions.\n\nThe\nfunction\nitself is returned as the value that represents the bank-account object.\n\nThis is precisely the\nmessage-passing style of programming that we saw in\nsection , although here we are using\nit in conjunction with the ability to modify local variables.\n\n```javascript\nThe function\n\tmake_account\n```\n\ncan be used as follows:\n\n```javascript\nmake_account_example\n make_account\n\nconst acc = make_account(100);\n```\n\n```javascript\nmake_account_example1\n make_account_example\n 50\n\nacc(\"withdraw\")(50);\n```\n\n```javascript\nmake_account_example1\n make_account_example2\n 'Insufficient funds'\n\nacc(\"withdraw\")(60);\n```\n\n```javascript\nmake_account_example2\n make_account_example3\n 90\n\nacc(\"deposit\")(40);\n```\n\n```javascript\nmake_account_example3\n make_account_example4\n 30\n\nacc(\"withdraw\")(60);\n```\n\nEach call to function, which is then applied to the specified\n\n```javascript\nmake_withdraw, another\n\tcall to make_account\n```\n\n```javascript\nmake_account\n\nconst acc2 = make_account(100);\n```\n\nwill produce a completely separate account object, which maintains its own local", - "token_count": 230, + "content": "First, however, we examine some variations on the theme established by\nnew_withdraw.\n\nThe following\nfunction, make_withdraw,\ncreates withdrawal processors.\n\nThe parameter\nin\nmake_withdraw\nspecifies the initial amount of money in the\naccount.\n\n```javascript\nmake_withdraw\n make_withdraw_define\n\nfunction make_withdraw(balance) {\n return amount => {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n };\n}\n```\n\nThe function make_withdraw can be used as follows to create two objects and :\n\n```javascript\nmake_withdraw_define\n make_withdraw\n\nconst W1 = make_withdraw(100);\nconst W2 = make_withdraw(100);\n```\n\n```javascript\nmake_withdraw_example1\n make_withdraw_define\n 50\n\nW1(50);\n\n50\n```\n\n```javascript\nmake_withdraw_example1\n make_withdraw_example2\n 30\n\nW2(70);\n\n30\n```\n\n```javascript\nmake_withdraw_example3\n make_withdraw_example2\n 'Insufficient funds'\n\nW2(40);\n\n\"Insufficient funds\"\n```\n\n```javascript\nmake_withdraw_example4\n make_withdraw_example3\n 10\n\nW1(40);\n\n10\n```\n\nObserve that and\nare completely independent objects, each\nwith its own local state variable.\n\nWithdrawals from one do not affect the other.\n\nWe can also create objects that handle\ndeposits as well as\nwithdrawals, and thus we can represent simple bank accounts.\n\nHere is\na\nfunction\nthat returns a bank-account object with a specified initial\nbalance:\n\n```javascript\nmake_account\n make_account_example_my\n\nfunction make_account(balance) {\n function withdraw(amount) {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n }\n function deposit(amount) {\n balance = balance + amount;\n return balance;\n }\n function dispatch(m) {\n return m === \"withdraw\"\n ? withdraw\n : m === \"deposit\"\n ? deposit\n : error(m, \"unknown request -- make_account\");\n }\n return dispatch;\n}\n```\n\n```javascript\nmake_account_example_my\n\nconst acc = make_account(100);\n\nacc(\"withdraw\")(50);\n```\n\nEach call to sets up an\nenvironment with a local state variable.\n\nWithin this environment, defines\nfunctions\nand\nthat access\nand an additional\nfunction\nthat takes a message as input and returns one of the two local\nfunctions.\n\nThe\nfunction\nitself is returned as the value that represents the bank-account object.", + "token_count": 301, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Assignment and Local State", @@ -3030,8 +4270,18 @@ "chunk_id": "Modularity_Objects_and_State_Local_State_Variables_4" }, { - "content": "As we shall see, introducing assignment into our programming language\nleads us into a thicket of difficult conceptual issues.\n\nNevertheless,\nviewing systems as\nfunction\n\nIt is not at all clear what is meant by chosen at random.\n\nWhat we presumably want is for successive calls to\nfunction\nrand_update\nthat has the property that if we start with a given number\n$x_{1}$ and form\n\n```javascript\n$x_2$ = rand_update($x_1$);\n$x_3$ = rand_update($x_2$);\n```\n\nthen the sequence of values $x_1, x_2, x_3, \\ldots$ , will have the desired statistical properties.\n\nWe can implement\nfunction\nwith a local state variable\nrandom_init.\n\nEach call to\nrand_update\nof the current value of\n\n```javascript\nrand_update\n\n// A very simple rand_update function computes a number\n// from 0 (inclusive) to 200560490131 (a large prime)\n// from a value x by multiplying it with a constant a,\n// adding a constant b, and then taking the remainder\n// of dividing it by the large prime. We used it here\n// for illustration only, and do not claim any\n// statistical properties.\nconst m = 200560490131;\nconst a = 1103515245;\nconst b = 12345;\n\nfunction rand_update(x) {\n return (a * x + b) % m;\n}\n```\n\n```javascript\nrand_definition\n rand_update\n random_init\n rand_example\n 40083849805\n\nfunction make_rand() {\n let x = random_init;\n return () => {\n x = rand_update(x);\n return x;\n };\n}\nconst rand = make_rand();\n```\n\n```javascript\nrandom_init\n\nconst random_init = 123456789;\n```\n\n```javascript\nrand_example\n\ndisplay(rand());\ndisplay(rand());\ndisplay(rand());\n\nrand();\nrand();\nrand();\n```\n\nOf course, we could generate the same sequence of random numbers\nwithout using assignment by simply calling\nrand_update\ndirectly.\n\nHowever, this would mean that any part of our program that used\nrandom numbers would have to explicitly remember the current value of\nrand_update.\n\nTo realize what an annoyance this would be, consider using random numbers\nto implement a technique called\nMonte Carlo simulation.", - "token_count": 305, + "content": "The\nfunction\nitself is returned as the value that represents the bank-account object.\n\nThe function make_account can be used as follows:\n\n```javascript\nmake_account_example\n make_account\n\nconst acc = make_account(100);\n```\n\n```javascript\nmake_account_example1\n make_account_example\n 50\n\nacc(\"withdraw\")(50);\n\n50\n```\n\n```javascript\nmake_account_example1\n make_account_example2\n 'Insufficient funds'\n\nacc(\"withdraw\")(60);\n\n\"Insufficient funds\"\n```\n\n```javascript\nmake_account_example2\n make_account_example3\n 90\n\nacc(\"deposit\")(40);\n\n90\n```\n\n```javascript\nmake_account_example3\n make_account_example4\n 30\n\nacc(\"withdraw\")(60);\n\n30\n```\n\nEach call to returns the locally defined\nor\nfunction,\nwhich is then applied to the specified.\n\nAs was the case with\nmake_withdraw, another call to make_account\n\n```javascript\nmake_account\n\nconst acc2 = make_account(100);\n```\n\nwill produce a completely separate account object, which maintains its own local.", + "token_count": 104, + "has_code": true, + "chapter": "Modularity, Objects, and State", + "section": "Assignment and Local State", + "subsection": "Local State Variables", + "chunk_index": 5, + "chunk_id": "Modularity_Objects_and_State_Local_State_Variables_5" + }, + { + "content": "As we shall see, introducing assignment into our programming language\nleads us into a thicket of difficult conceptual issues.\n\nNevertheless,\nviewing systems as\ncollections of objects with local state is a\npowerful technique for maintaining a\nmodular design.\n\nAs a simple\nexample, consider the design of a\nfunction\nthat, whenever\nit is called, returns an integer chosen at random.\n\nIt is not at all clear what is meant by chosen at random.\n\nWhat we presumably want is for successive calls to\nto produce a sequence of numbers that has\nstatistical properties of uniform distribution.\n\nWe will not discuss methods\nfor generating suitable sequences here.\n\nRather, let us assume that we have a\nfunction\nrand_update\nthat has the property that if we start with a given number\n$x_{1}$ and form\n$x_2$ = rand_update($x_1$); $x_3$ = rand_update($x_2$);\nthen the sequence of values\n$x_1, x_2, x_3, \\ldots$ , will have the desired\nstatistical properties.\n\nWe can implement as a\nfunction\nwith a local state variable that is\ninitialized to some fixed value\nrandom_init.\n\nEach call to computes\nrand_update\nof the current value of , returns this as the\nrandom number, and also stores this as the new value of.\n\n```javascript\nrand_update\n\n// A very simple rand_update function computes a number\n// from 0 (inclusive) to 200560490131 (a large prime)\n// from a value x by multiplying it with a constant a,\n// adding a constant b, and then taking the remainder\n// of dividing it by the large prime. We used it here\n// for illustration only, and do not claim any\n// statistical properties.\nconst m = 200560490131;\nconst a = 1103515245;\nconst b = 12345;\n\nfunction rand_update(x) {\n return (a * x + b) % m;\n}\n```", + "token_count": 287, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Assignment and Local State", @@ -3040,8 +4290,8 @@ "chunk_id": "Modularity_Objects_and_State_The_Benefits_of_Introducing_Assignment_1" }, { - "content": "To realize what an annoyance this would be, consider using random numbers\nto implement a technique called\nMonte Carlo simulation.\n\nThe Monte Carlo method consists of choosing sample experiments at random\nfrom a large set and then making deductions on the basis of the\nprobabilities estimated from tabulating the results of those experiments.\n\nFor example, we can approximate\n$\\pi$ using the fact that\n$6/\\pi^2$ is the probability that two integers\nchosen at random will have no factors in common; that is, that their\ngreatest common divisor will be 1. $\\pi$ , we perform\na large number of experiments.\n\nIn each experiment we choose two integers at\nrandom and perform a test\n$6/\\pi^2$ , and from this\nwe obtain our approximation to $\\pi$.\n\nThe heart of our program is a\nfunction\nmonte_carlo,\nwhich takes as arguments the number of times to try an experiment, together\nwith the experiment, represented as a no-argument\nfunction\nthat will return either true or false each time it is run.\n\nThe function\nruns the experiment for the designated number of trials and returns a\nnumber telling the fraction of the trials in which the experiment was\nfound to be true.\n\n```javascript\nmonte_carlo\n rand_definition\n gcd_definition\n estimate_pi_example\n 3.1408877612819492\n\nfunction estimate_pi(trials) {\n return math_sqrt(6 / monte_carlo(trials, dirichlet_test));\n}\nfunction dirichlet_test() {\n return gcd(rand(), rand()) === 1;\n}\nfunction monte_carlo(trials, experiment) {\n function iter(trials_remaining, trials_passed) {\n return trials_remaining === 0\n ? trials_passed / trials\n : experiment()\n ? iter(trials_remaining - 1, trials_passed + 1)\n : iter(trials_remaining - 1, trials_passed);\n }\n return iter(trials, 0);\n}\n```\n\n```javascript\nestimate_pi_example\n\nestimate_pi(10000);\n```\n\nNow let us try the same computation using rand_update directly rather than", - "token_count": 270, + "content": "Each call to computes\nrand_update\nof the current value of , returns this as the\nrandom number, and also stores this as the new value of.\n\n```javascript\nrandom_init\n\nconst random_init = 123456789;\n```\n\n```javascript\nrand_example\n\ndisplay(rand());\ndisplay(rand());\ndisplay(rand());\n\nrand();\nrand();\nrand();\n```\n\nOf course, we could generate the same sequence of random numbers\nwithout using assignment by simply calling\nrand_update\ndirectly.\n\nHowever, this would mean that any part of our program that used\nrandom numbers would have to explicitly remember the current value of\nto be passed as an argument to\nrand_update.\n\nTo realize what an annoyance this would be, consider using random numbers\nto implement a technique called\nMonte Carlo simulation.\n\nThe Monte Carlo method consists of choosing sample experiments at random\nfrom a large set and then making deductions on the basis of the\nprobabilities estimated from tabulating the results of those experiments.\n\nFor example, we can approximate\n$\\pi$ using the fact that\n$6/\\pi^2$ is the probability that two integers\nchosen at random will have no factors in common; that is, that their\ngreatest common divisor will be 1.\n\nTo obtain the approximation to $\\pi$ , we perform\na large number of experiments.\n\nIn each experiment we choose two integers at\nrandom and perform a test\nto see if their GCD is 1.\n\nThe fraction of times that the test is passed\ngives us our estimate of $6/\\pi^2$ , and from this\nwe obtain our approximation to $\\pi$.\n\nThe heart of our program is a\nfunction\nmonte_carlo,\nwhich takes as arguments the number of times to try an experiment, together\nwith the experiment, represented as a no-argument\nfunction\nthat will return either true or false each time it is run.", + "token_count": 282, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Assignment and Local State", @@ -3050,8 +4300,8 @@ "chunk_id": "Modularity_Objects_and_State_The_Benefits_of_Introducing_Assignment_2" }, { - "content": "Now let us try the same computation using rand_update directly rather than\n\n```javascript\nestimate_pi_alternative\n rand_definition\n gcd_definition\n estimate_pi_example\n 3.1408877612819492\n\nfunction estimate_pi(trials) {\n return math_sqrt(6 / random_gcd_test(trials, random_init));\n}\nfunction random_gcd_test(trials, initial_x) {\n function iter(trials_remaining, trials_passed, x) {\n const x1 = rand_update(x);\n const x2 = rand_update(x1);\n return trials_remaining === 0\n ? trials_passed / trials\n : gcd(x1, x2) === 1\n ? iter(trials_remaining - 1, trials_passed + 1, x2)\n : iter(trials_remaining - 1, trials_passed, x2);\n }\n return iter(trials, 0, initial_x);\n}\n```\n\nWhile the program is still simple, it betrays some painful breaches of\nmodularity.\n\nIn our first version of the program, using\nmonte_carlo\nfunction\nthat takes as an argument an arbitrary\nfunction.\n\nIn our second version of the program, with no local state for the\nrandom-number generator,\nrandom_gcd_test\nmust explicitly manipulate the random numbers\nrand_update.\n\nThis explicit handling of the random numbers intertwines the structure of\naccumulating test results with the fact that our particular experiment uses\ntwo random numbers, whereas other Monte Carlo experiments might use one\nrandom number or three.\n\nEven the top-level\nfunction\nestimate_pi\nhas to be concerned with supplying an initial random number.\n\nThe fact that\nthe random-number generator s insides are leaking out into other parts\nof the program makes it difficult for us to isolate the Monte Carlo idea so\nthat it can be applied to other tasks.\n\nIn the first version of the program,\nassignment encapsulates the state of the random-number generator within the\nfunction,\nso that the details of random-number generation remain independent of the\nrest of the program.\n\nThe general phenomenon illustrated by the Monte Carlo example is this: From\nthe point of view of one part of a complex process, the other parts appear\nto change with time.\n\nThey have hidden time-varying local state.", - "token_count": 291, + "content": "The heart of our program is a\nfunction\nmonte_carlo,\nwhich takes as arguments the number of times to try an experiment, together\nwith the experiment, represented as a no-argument\nfunction\nthat will return either true or false each time it is run.\n\n```javascript\nmonte_carlo\n rand_definition\n gcd_definition\n estimate_pi_example\n 3.1408877612819492\n\nfunction estimate_pi(trials) {\n return math_sqrt(6 / monte_carlo(trials, dirichlet_test));\n}\nfunction dirichlet_test() {\n return gcd(rand(), rand()) === 1;\n}\nfunction monte_carlo(trials, experiment) {\n function iter(trials_remaining, trials_passed) {\n return trials_remaining === 0\n ? trials_passed / trials\n : experiment()\n ? iter(trials_remaining - 1, trials_passed + 1)\n : iter(trials_remaining - 1, trials_passed);\n }\n return iter(trials, 0);\n}\n```\n\n```javascript\nestimate_pi_example\n\nestimate_pi(10000);\n```\n\nNow let us try the same computation using rand_update directly rather than , the way we would be forced to proceed if we did not\n\nuse assignment to model local state:\n\n```javascript\nestimate_pi_alternative\n rand_definition\n gcd_definition\n estimate_pi_example\n 3.1408877612819492\n\nfunction estimate_pi(trials) {\n return math_sqrt(6 / random_gcd_test(trials, random_init));\n}\nfunction random_gcd_test(trials, initial_x) {\n function iter(trials_remaining, trials_passed, x) {\n const x1 = rand_update(x);\n const x2 = rand_update(x1);\n return trials_remaining === 0\n ? trials_passed / trials\n : gcd(x1, x2) === 1\n ? iter(trials_remaining - 1, trials_passed + 1, x2)\n : iter(trials_remaining - 1, trials_passed, x2);\n }\n return iter(trials, 0, initial_x);\n}\n```\n\nWhile the program is still simple, it betrays some painful breaches of\nmodularity.\n\nIn our first version of the program, using\n, we can express the Monte Carlo method\ndirectly as a general\nmonte_carlo\nfunction\nthat takes as an argument an arbitrary\nfunction.\n\nIn our second version of the program, with no local state for the\nrandom-number generator,\nrandom_gcd_test\nmust explicitly manipulate the random numbers\nand and\nrecycle through the iterative loop as the\nnew input to\nrand_update.", + "token_count": 281, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Assignment and Local State", @@ -3060,8 +4310,8 @@ "chunk_id": "Modularity_Objects_and_State_The_Benefits_of_Introducing_Assignment_3" }, { - "content": "They have hidden time-varying local state.\n\nIf we wish\nto write computer programs whose structure reflects this decomposition, we\nmake computational objects (such as bank accounts and random-number\ngenerators) whose behavior changes with time.\n\nWe model state with local\nstate variables, and we model the changes of state with assignments to those\nvariables.\n\nIt is tempting to conclude this discussion by saying that, by introducing\nassignment and the technique of hiding state in local variables, we are able\nto structure systems in a more modular fashion than if all state had to be\nmanipulated explicitly, by passing additional parameters.\n\nUnfortunately,\nas we shall see, the story is not so simple.", - "token_count": 110, + "content": "In our second version of the program, with no local state for the\nrandom-number generator,\nrandom_gcd_test\nmust explicitly manipulate the random numbers\nand and\nrecycle through the iterative loop as the\nnew input to\nrand_update.\n\nEven the top-level\nfunction\nestimate_pi\nhas to be concerned with supplying an initial random number.\n\nThe fact that\nthe random-number generator s insides are leaking out into other parts\nof the program makes it difficult for us to isolate the Monte Carlo idea so\nthat it can be applied to other tasks.\n\nIn the first version of the program,\nassignment encapsulates the state of the random-number generator within the\nfunction,\nso that the details of random-number generation remain independent of the\nrest of the program.\n\nThe general phenomenon illustrated by the Monte Carlo example is this: From\nthe point of view of one part of a complex process, the other parts appear\nto change with time.\n\nThey have hidden time-varying local state.\n\nIf we wish\nto write computer programs whose structure reflects this decomposition, we\nmake computational objects (such as bank accounts and random-number\ngenerators) whose behavior changes with time.\n\nWe model state with local\nstate variables, and we model the changes of state with assignments to those\nvariables.\n\nIt is tempting to conclude this discussion by saying that, by introducing\nassignment and the technique of hiding state in local variables, we are able\nto structure systems in a more modular fashion than if all state had to be\nmanipulated explicitly, by passing additional parameters.\n\nUnfortunately,\nas we shall see, the story is not so simple.", + "token_count": 261, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Assignment and Local State", @@ -3070,8 +4320,8 @@ "chunk_id": "Modularity_Objects_and_State_The_Benefits_of_Introducing_Assignment_4" }, { - "content": "As we have seen,\nassignment\nenables us to model objects\nthat have local state.\n\nHowever, this advantage comes at a price.\n\nOur\nprogramming language can no longer be interpreted in terms of the\nsubstitution model of\nfunction\napplication that we introduced in\nsection.\n\nMoreover, no simple\nmodel with nice mathematical properties can be an adequate\nframework for dealing with objects and assignment in programming languages.\n\nSo long as we do not use assignments, two evaluations of the same\nfunction\nwith the same arguments will produce the same result, so that\nfunctions\ncan be viewed as computing mathematical functions.\n\nProgramming without any\nuse of assignments, as we did throughout the first two chapters of this\nbook, is accordingly known as\nfunctional programming.\n\nfunction of section that does not bother to check for an insufficient amount:\n\n```javascript\nmake_simplified_withdraw\n make_simplified_withdraw_example\n\nfunction make_simplified_withdraw(balance) {\n return amount => {\n balance = balance - amount;\n return balance;\n };\n}\n```\n\n```javascript\nmake_simplified_withdraw_example\n make_simplified_withdraw\n\nconst W = make_simplified_withdraw(25);\n```\n\n```javascript\nmake_simplified_withdraw_example1\n make_simplified_withdraw\n make_simplified_withdraw_example\n 5\n\nW(20);\n```\n\n```javascript\nmake_simplified_withdraw_example2\n make_simplified_withdraw\n make_simplified_withdraw_example\n make_simplified_withdraw_example1\n -5\n\nW(10);\n```\n\nCompare this function with the following function, which does not use assignment:\n\n```javascript\nmake_decrementer\n\nfunction make_decrementer(balance) {\n return amount => balance - amount;\n}\n```\n\n```javascript\nThe function\n make_decrementer\n```\n\nreturns a function that subtracts its input from a designated amount\n\n```javascript\nmake_decrementer\n make_decrementer_example\n\nconst D = make_decrementer(25);\n```\n\n```javascript\nmake_decrementer_example\n make_decrementer_example1\n 5\n\nD(20);\n```\n\n```javascript\nmake_decrementer_example1\n make_decrementer_example2\n 15\n\nD(10);\n```\n\nWe can use the substitution model to explain how\n\n```javascript\nmake_decrementer\n 5\n\nmake_decrementer(25)(20)\n\nmake_decrementer(25)(20);\n```\n\nWe first simplify the\nfunction expression of the application\nby substituting\n$25$ for\nmake_decrementer.\n\nThis reduces the\nexpression to\n\n```javascript\n(amount => 25 - amount)(20)\n```\n\nNow we apply the function by substituting 20 for lambda expression:\n\n```javascript\n5\n\n25 - 20\n\n25 - 20;\n```\n\nThe final answer is 5.\n\nObserve, however, what happens if we attempt a similar substitution analysis with\n\n```javascript\nmake_simplified_withdraw\n\nmake_simplified_withdraw(25)(20)\n```", - "token_count": 320, + "content": "As we have seen,\nassignment\nenables us to model objects\nthat have local state.\n\nHowever, this advantage comes at a price.\n\nOur\nprogramming language can no longer be interpreted in terms of the\nsubstitution model of\nfunction\napplication that we introduced in\nsection.\n\nMoreover, no simple\nmodel with nice mathematical properties can be an adequate\nframework for dealing with objects and assignment in programming languages.\n\nSo long as we do not use assignments, two evaluations of the same\nfunction\nwith the same arguments will produce the same result, so that\nfunctions\ncan be viewed as computing mathematical functions.\n\nProgramming without any\nuse of assignments, as we did throughout the first two chapters of this\nbook, is accordingly known as\nfunctional programming.\n\nTo understand how assignment complicates matters, consider a simplified version of the function of section that does not bother to check for an insufficient amount:\n\n```javascript\nmake_simplified_withdraw\n make_simplified_withdraw_example\n\nfunction make_simplified_withdraw(balance) {\n return amount => {\n balance = balance - amount;\n return balance;\n };\n}\n```\n\n```javascript\nmake_simplified_withdraw_example\n make_simplified_withdraw\n\nconst W = make_simplified_withdraw(25);\n```\n\n```javascript\nmake_simplified_withdraw_example1\n make_simplified_withdraw\n make_simplified_withdraw_example\n 5\n\nW(20);\n\n5\n```\n\n```javascript\nmake_simplified_withdraw_example2\n make_simplified_withdraw\n make_simplified_withdraw_example\n make_simplified_withdraw_example1\n -5\n\nW(10);\n\n-5\n```\n\nCompare this function with the following function, which does not use assignment:\n\n```javascript\nmake_decrementer\n\nfunction make_decrementer(balance) {\n return amount => balance - amount;\n}\n```\n\nThe function make_decrementer returns a function that subtracts its input from a designated amount , but there is no accumulated effect over successive calls, as\n\nwith :\n\n```javascript\nmake_decrementer\n make_decrementer_example\n\nconst D = make_decrementer(25);\n```\n\n```javascript\nmake_decrementer_example\n make_decrementer_example1\n 5\n\nD(20);\n\n5\n```\n\n```javascript\nmake_decrementer_example1\n make_decrementer_example2\n 15\n\nD(10);\n\n15\n```\n\nWe can use the substitution model to explain how\nworks.\n\nFor instance, let us\nanalyze the evaluation of the expression\n\n```javascript\nmake_decrementer\n 5\n\nmake_decrementer(25)(20)\n\nmake_decrementer(25)(20);\n```\n\nWe first simplify the\nfunction expression of the application\nby substituting\n$25$ for in\nthe body of\nmake_decrementer.\n\nThis reduces the\nexpression to\n\n```javascript\n(amount => 25 - amount)(20)\n```", + "token_count": 321, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Assignment and Local State", @@ -3080,8 +4330,8 @@ "chunk_id": "Modularity_Objects_and_State_The_Costs_of_Introducing_Assignment_1" }, { - "content": "Observe, however, what happens if we attempt a similar substitution analysis with\n\nWe first simplify the function expression by substituting 25 for the body of expression to\n\n```javascript\n(amount => {\n balance = 25 - amount;\n return 25;\n })(20)\n```\n\nNow we apply the function by substituting 20 for lambda expression:\n\n```javascript\nbalance = 25 - 20;\nreturn 25;\n```\n\nIf we adhered to the substitution model, we would have to say that the meaning of the function application is to first set assignment) from\n\nthe second occurrence of assignment), and the substitution model cannot do this.\n\nThe trouble here is that substitution is based ultimately on the notion that\n\n```javascript\nthe name in our language are essentially\n\tsymbols for values.\n```\n\n```javascript\nThis worked well for constants.\n\tBut a variable, whose value can change with assignment, cannot simply\n\tbe a name for a value. A variable somehow refers to a place where a\n\tvalue can be stored, and the value stored at this place can change.\n```\n\nIn section we will see how environments play this role of place in our computational model.\n\nThe issue surfacing here is more profound than the mere breakdown of a\nparticular model of computation.\n\nAs soon as we introduce change into\nour computational models, many notions that were previously\nstraightforward become problematical.\n\nConsider the concept of two\nthings being the same.\n\nSuppose we call make_decrementer twice with the same argument to create two functions:\n\n```javascript\nmake_decrementer\n\nconst D1 = make_decrementer(25);\n\nconst D2 = make_decrementer(25);\n```\n\nAre\neach is a\nfunction\nthat subtracts its input from 25.\n\nIn fact,\n\nContrast this with making two calls to make_simplified_withdraw:\n\n```javascript\nmake_simplified_withdraw\n make_simplified_withdraw_example3\n\nconst W1 = make_simplified_withdraw(25);\n\nconst W2 = make_simplified_withdraw(25);\n```\n\nAre\n\n```javascript\nmake_simplified_withdraw_example3\n make_simplified_withdraw_example4\n 5\n\nW1(20);\n```\n\n```javascript\nmake_simplified_withdraw_example4\n make_simplified_withdraw_example5\n -15\n\nW1(20);\n```\n\n```javascript\nmake_simplified_withdraw_example5\n make_simplified_withdraw_example6\n 5\n\nW2(20);\n```", - "token_count": 304, + "content": "This reduces the\nexpression to\n\n```javascript\n5\n\n25 - 20\n\n25 - 20;\n```\n\nThe final answer is 5.\n\nObserve, however, what happens if we attempt a similar substitution analysis with :\n\n```javascript\nmake_simplified_withdraw\n\nmake_simplified_withdraw(25)(20)\n```\n\nWe first simplify the\nfunction expression\nby substituting 25 for\nin\nthe body of.\n\nThis reduces the\nexpression\nto\n\n```javascript\n(amount => {\n balance = 25 - amount;\n return 25;\n })(20)\n```\n\nNow we apply the function by substituting 20 for in the body of the lambda expression:\n\n```javascript\nbalance = 25 - 20;\nreturn 25;\n```\n\nIf we adhered to the substitution model, we would have to say that the\nmeaning of the\nfunction\napplication is to first set to 5 and\nthen return 25 as the value of the expression.\n\nThis gets the wrong answer.\n\nIn order to get the correct answer, we would have to somehow distinguish the\nfirst occurrence of (before the effect\nof the\nassignment)\nfrom the second occurrence of\n(after the effect of the\nassignment),\nand the substitution model cannot do this.\n\nThe trouble here is that substitution is based ultimately on the notion that\nthe name in our language are essentially symbols for values.\n\nThis worked well for constants.\n\nBut a variable, whose value can change with assignment, cannot simply be a name for a value.\n\nA variable somehow refers to a place where a value can be stored, and the value stored at this place can change.\n\nIn section we will see how\nenvironments play this role of place in our computational\nmodel.\n\nThe issue surfacing here is more profound than the mere breakdown of a\nparticular model of computation.\n\nAs soon as we introduce change into\nour computational models, many notions that were previously\nstraightforward become problematical.\n\nConsider the concept of two\nthings being the same.", + "token_count": 301, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Assignment and Local State", @@ -3090,7 +4340,7 @@ "chunk_id": "Modularity_Objects_and_State_The_Costs_of_Introducing_Assignment_2" }, { - "content": "Are\n\nEven though equal in the sense that they are both created by evaluating the same expression, make_simplified_withdraw(25), it is not true that\n\nA language that supports the concept that equals can be substituted\nfor equals in an expression without changing the value of the\nexpression is said to be\nreferentially transparent.\n\nReferential transparency is violated\nwhen we include\nassignment\nin our computer language.\n\nThis makes it tricky to determine when we can\nsimplify expressions by substituting equivalent expressions.\n\nConsequently,\nreasoning about programs that use assignment becomes drastically more\ndifficult.\n\nOnce we forgo referential transparency, the notion of what it means for\ncomputational objects to be the same becomes difficult to\ncapture in a formal way.\n\nIndeed, the meaning of same in the\nreal world that our programs model is hardly clear in itself.\n\nIn general,\nwe can determine that two apparently identical objects are indeed\nthe same one only by modifying one object and then observing\nwhether the other object has changed in the same way.\n\nBut how can we tell\nif an object has changed other than by observing the\nsame object twice and seeing whether some property of the\nobject differs from one observation to the next?\n\nThus, we cannot determine\nchange without some a priori notion of\nsameness, and we cannot determine sameness without observing\nthe effects of change.\n\nAs an example of how this issue arises in programming, consider the\nsituation where Peter and Paul have a\n100 in\nit.\n\nThere is a substantial difference between modeling this as\n\n```javascript\nmake_account\n 50\n\nconst peter_acc = make_account(100);\nconst paul_acc = make_account(100);\n\nconst peter_acc = make_account(100);\nconst paul_acc = make_account(100);\n\npeter_acc(\"withdraw\")(10);\npeter_acc(\"withdraw\")(20);\npaul_acc(\"withdraw\")(50);\n```\n\nand modeling it as\n\n```javascript\nmake_account\n 20\n\nconst peter_acc = make_account(100);\nconst paul_acc = peter_acc;\n\nconst peter_acc = make_account(100);\nconst paul_acc = peter_acc;\npeter_acc(\"withdraw\")(10);\npeter_acc(\"withdraw\")(20);\npaul_acc(\"withdraw\")(50);\n```", + "content": "Consider the concept of two\nthings being the same.\n\n```javascript\nmake_decrementer\n\nconst D1 = make_decrementer(25);\n\nconst D2 = make_decrementer(25);\n```\n\nAre\nand\nthe same?\n\nAn acceptable answer is yes, because\nand\nhave the same computational behavior each is a\nfunction\nthat subtracts its input from 25.\n\nIn fact,\ncould be substituted for\nin any computation without changing the result.\n\nContrast this with making two calls to make_simplified_withdraw:\n\n```javascript\nmake_simplified_withdraw\n make_simplified_withdraw_example3\n\nconst W1 = make_simplified_withdraw(25);\n\nconst W2 = make_simplified_withdraw(25);\n```\n\nAre and\nthe same?\n\nSurely not, because calls to\nand\nhave distinct effects, as shown by the following sequence of interactions:\n\n```javascript\nmake_simplified_withdraw_example3\n make_simplified_withdraw_example4\n 5\n\nW1(20);\n\n5\n```\n\n```javascript\nmake_simplified_withdraw_example4\n make_simplified_withdraw_example5\n -15\n\nW1(20);\n\n-15\n```\n\n```javascript\nmake_simplified_withdraw_example5\n make_simplified_withdraw_example6\n 5\n\nW2(20);\n\n5\n```\n\nEven though and are equal in the sense that they are both created by evaluating the same expression, make_simplified_withdraw(25), it is not true that could\n\nbe substituted for in any expression without changing the result of evaluating the expression.\n\nA language that supports the concept that equals can be substituted\nfor equals in an expression without changing the value of the\nexpression is said to be\nreferentially transparent.\n\nReferential transparency is violated\nwhen we include\nassignment\nin our computer language.\n\nThis makes it tricky to determine when we can\nsimplify expressions by substituting equivalent expressions.\n\nConsequently,\nreasoning about programs that use assignment becomes drastically more\ndifficult.\n\nOnce we forgo referential transparency, the notion of what it means for\ncomputational objects to be the same becomes difficult to\ncapture in a formal way.\n\nIndeed, the meaning of same in the\nreal world that our programs model is hardly clear in itself.\n\nIn general,\nwe can determine that two apparently identical objects are indeed\nthe same one only by modifying one object and then observing\nwhether the other object has changed in the same way.", "token_count": 304, "has_code": true, "chapter": "Modularity, Objects, and State", @@ -3100,8 +4350,8 @@ "chunk_id": "Modularity_Objects_and_State_The_Costs_of_Introducing_Assignment_3" }, { - "content": "and modeling it as\n\nIn the first situation, the two bank accounts are distinct.\n\nTransactions made by Peter will not affect Paul s account, and vice\nversa.\n\nIn the second situation, however, we have defined\npaul_acc\nto be the same thing as\npeter_acc.\n\nIn effect, Peter and Paul now have a joint bank account, and if Peter makes\na withdrawal from\npeter_acc\nPaul will observe less money in\npaul_acc.\n\nThese two similar but distinct situations can cause confusion in building\ncomputational models.\n\nWith the shared account, in particular, it can be\nespecially confusing that there is one object (the bank account) that has\ntwo different names\n\n```javascript\n(peter_acc and\n\tpaul_acc);\n```\n\nif we are searching for all the places in our program where paul_acc can be changed, we must remember to look also at things that\n\nchange peter_acc.\n\nWith reference to the above remarks on sameness and\nchange, observe that if Peter and Paul could only examine\ntheir bank balances, and could not perform operations that changed the\nbalance, then the issue of whether the two accounts are distinct would be\nmoot.\n\nIn general, so long as we never modify data objects, we can regard a\ncompound data object to be precisely the totality of its pieces.\n\nFor\nexample, a rational number is determined by giving its numerator and\nits denominator.\n\nBut this view is no longer valid in the presence of\nchange, where a compound data object has an identity that is\nsomething different from the pieces of which it is composed.\n\nA bank\naccount is still the same bank account even if we change the\nbalance by making a withdrawal; conversely, we could have two\ndifferent bank accounts with the same state information.", - "token_count": 285, + "content": "In general,\nwe can determine that two apparently identical objects are indeed\nthe same one only by modifying one object and then observing\nwhether the other object has changed in the same way.\n\nThus, we cannot determine\nchange without some a priori notion of\nsameness, and we cannot determine sameness without observing\nthe effects of change.\n\nAs an example of how this issue arises in programming, consider the\nsituation where Peter and Paul have a\nbank account with 100 in\nit.\n\nThere is a substantial difference between modeling this as\n\n```javascript\nmake_account\n 50\n\nconst peter_acc = make_account(100);\nconst paul_acc = make_account(100);\n\nconst peter_acc = make_account(100);\nconst paul_acc = make_account(100);\n\npeter_acc(\"withdraw\")(10);\npeter_acc(\"withdraw\")(20);\npaul_acc(\"withdraw\")(50);\n```\n\nand modeling it as\n\n```javascript\nmake_account\n 20\n\nconst peter_acc = make_account(100);\nconst paul_acc = peter_acc;\n\nconst peter_acc = make_account(100);\nconst paul_acc = peter_acc;\npeter_acc(\"withdraw\")(10);\npeter_acc(\"withdraw\")(20);\npaul_acc(\"withdraw\")(50);\n```\n\nIn the first situation, the two bank accounts are distinct.\n\nTransactions made by Peter will not affect Paul s account, and vice\nversa.\n\nIn the second situation, however, we have defined\npaul_acc\nto be the same thing as\npeter_acc.\n\nIn effect, Peter and Paul now have a joint bank account, and if Peter makes\na withdrawal from\npeter_acc\nPaul will observe less money in\npaul_acc.\n\nThese two similar but distinct situations can cause confusion in building\ncomputational models.\n\nWith the shared account, in particular, it can be\nespecially confusing that there is one object (the bank account) that has\ntwo different names\n(peter_acc and paul_acc);\nif we are searching for all the places in our program where\npaul_acc\ncan be changed, we must remember to look also at things that change\npeter_acc.", + "token_count": 272, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Assignment and Local State", @@ -3110,8 +4360,8 @@ "chunk_id": "Modularity_Objects_and_State_The_Costs_of_Introducing_Assignment_4" }, { - "content": "A bank\naccount is still the same bank account even if we change the\nbalance by making a withdrawal; conversely, we could have two\ndifferent bank accounts with the same state information.\n\nThis\ncomplication is a consequence, not of our programming language, but of\nour perception of a bank account as an object.\n\nWe do not, for\nexample, ordinarily regard a rational number as a changeable object\nwith identity, such that we could change the numerator and still have\nthe same rational number.\n\nIn contrast to functional programming, programming that makes extensive use\nof assignment is known as\nimperative programming.\n\nIn addition to raising complications about\ncomputational models, programs written in imperative style are susceptible\nto bugs that cannot occur in functional programs.\n\nFor example, recall the\niterative factorial program from\n\n```javascript\nsection\n\t(here using a conditional statement instead of a conditional\n\texpression):\n```\n\n```javascript\nfactorial_iterative\n factorial_example\n 120\n\nfunction factorial(n) {\n function iter(product, counter) {\n if (counter > n) {\n return product;\n } else {\n return iter(counter * product,\n counter + 1);\n }\n }\n return iter(1, 1);\n}\n```\n\nInstead of passing arguments in the internal iterative loop, we could adopt a more imperative style by using explicit assignment to update the values of\n\nthe variables\n\n```javascript\nfactorial_imperative\n factorial_example\n 120\n\nfunction factorial(n) {\n let product = 1;\n let counter = 1;\n function iter() {\n if (counter > n) {\n return product;\n } else {\n product = counter * product;\n counter = counter + 1;\n return iter();\n }\n }\n return iter();\n}\n```\n\nThis does not change the results produced by the program, but it does\nintroduce a subtle trap.\n\nHow do we decide the order of the assignments?\n\nAs it happens, the program is correct as written.\n\nBut writing the\nassignments in the opposite order\n\n```javascript\ncounter = counter + 1;\nproduct = counter * product;\n```", - "token_count": 307, + "content": "With the shared account, in particular, it can be\nespecially confusing that there is one object (the bank account) that has\ntwo different names\n(peter_acc and paul_acc);\nif we are searching for all the places in our program where\npaul_acc\ncan be changed, we must remember to look also at things that change\npeter_acc.\n\nIn general, so long as we never modify data objects, we can regard a\ncompound data object to be precisely the totality of its pieces.\n\nFor\nexample, a rational number is determined by giving its numerator and\nits denominator.\n\nBut this view is no longer valid in the presence of\nchange, where a compound data object has an identity that is\nsomething different from the pieces of which it is composed.\n\nA bank\naccount is still the same bank account even if we change the\nbalance by making a withdrawal; conversely, we could have two\ndifferent bank accounts with the same state information.\n\nThis\ncomplication is a consequence, not of our programming language, but of\nour perception of a bank account as an object.\n\nWe do not, for\nexample, ordinarily regard a rational number as a changeable object\nwith identity, such that we could change the numerator and still have\nthe same rational number.\n\nIn contrast to functional programming, programming that makes extensive use\nof assignment is known as\nimperative programming.\n\nIn addition to raising complications about\ncomputational models, programs written in imperative style are susceptible\nto bugs that cannot occur in functional programs.\n\nFor example, recall the\niterative factorial program from\nsection (here using a conditional statement instead of a conditional expression):\n\n```javascript\nfactorial_iterative\n factorial_example\n 120\n\nfunction factorial(n) {\n function iter(product, counter) {\n if (counter > n) {\n return product;\n } else {\n return iter(counter * product,\n counter + 1);\n }\n }\n return iter(1, 1);\n}\n```", + "token_count": 302, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Assignment and Local State", @@ -3120,9 +4370,9 @@ "chunk_id": "Modularity_Objects_and_State_The_Costs_of_Introducing_Assignment_5" }, { - "content": "But writing the\nassignments in the opposite order\n\nwould have produced a different,\n\nThe complexity of imperative programs becomes even worse if we consider\napplications in which several processes execute concurrently.\n\nWe will\nreturn to this in section.\n\nFirst, however, we will address the issue of providing a computational\nmodel for expressions that involve assignment, and explore the uses of\nobjects with local state in designing simulations.", - "token_count": 67, - "has_code": false, + "content": "For example, recall the\niterative factorial program from\nsection (here using a conditional statement instead of a conditional expression):\n\nthe variables and :\n\n```javascript\nfactorial_imperative\n factorial_example\n 120\n\nfunction factorial(n) {\n let product = 1;\n let counter = 1;\n function iter() {\n if (counter > n) {\n return product;\n } else {\n product = counter * product;\n counter = counter + 1;\n return iter();\n }\n }\n return iter();\n}\n```\n\nThis does not change the results produced by the program, but it does\nintroduce a subtle trap.\n\nHow do we decide the order of the assignments?\n\nAs it happens, the program is correct as written.\n\nBut writing the\nassignments in the opposite order\n\n```javascript\ncounter = counter + 1;\nproduct = counter * product;\n```\n\nwould have produced a different,\nincorrect result.\n\nIn general, programming\nwith assignment forces us to carefully consider the relative orders of the\nassignments to make sure that each statement is using the correct version\nof the variables that have been changed.\n\nThis issue simply does not arise\nin functional programs.\n\nThe complexity of imperative programs becomes even worse if we consider\napplications in which several processes execute concurrently.\n\nWe will\nreturn to this in section.\n\nFirst, however, we will address the issue of providing a computational\nmodel for expressions that involve assignment, and explore the uses of\nobjects with local state in designing simulations.", + "token_count": 228, + "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Assignment and Local State", "subsection": "The Costs of Introducing Assignment", @@ -3130,8 +4380,8 @@ "chunk_id": "Modularity_Objects_and_State_The_Costs_of_Introducing_Assignment_6" }, { - "content": "We ve seen the power of computational objects with local state as\ntools for modeling.\n\nYet, as\nsection\nwarned, this power extracts a price: the loss of referential\ntransparency, giving rise to a thicket of questions about sameness and\nchange, and the need to abandon the substitution model of evaluation in\nfavor of the more intricate environment model.\n\nThe central issue lurking beneath the complexity of state, sameness,\nand change is that by introducing assignment we are forced to admit\ntime into our computational models.\n\nBefore we introduced\nassignment, all our programs were timeless, in the sense that any\nexpression that has a value always has the same value.\n\nIn contrast,\nrecall the example of modeling withdrawals from a bank account\nand returning the resulting balance,\nintroduced at the beginning of\nsection :\n\n```javascript\nwithdraw_example_3_4\n withdraw\n 75\n\nwithdraw(25);\n```\n\n```javascript\nwithdraw_example_3_4_second\n withdraw\n withdraw_example_3_4\n 50\n\nwithdraw(25);\n```\n\nHere successive evaluations of the same expression yield different\nvalues.\n\nThis behavior arises from the fact that the execution of\nassignments (in this case, assignments to the variable\nmoments in time\nwhen values change.\n\nThe result of evaluating an expression depends not\nonly on the expression itself, but also on whether the evaluation occurs\nbefore or after these moments.\n\nBuilding models in terms of computational\nobjects with local state forces us to confront time as an essential concept\nin programming.\n\nWe can go further in structuring computational models to match our\nperception of the physical world.\n\nObjects in the world do not change\none at a time in sequence.\n\nRather we perceive them as acting\nconcurrently all at once.\n\nSo it is often natural to\nmodel systems as collections of\nthreads (sequences of computational steps)\nthat execute concurrently.", - "token_count": 284, + "content": "We ve seen the power of computational objects with local state as\ntools for modeling.\n\nYet, as\nsection\nwarned, this power extracts a price: the loss of referential\ntransparency, giving rise to a thicket of questions about sameness and\nchange, and the need to abandon the substitution model of evaluation in\nfavor of the more intricate environment model.\n\nThe central issue lurking beneath the complexity of state, sameness,\nand change is that by introducing assignment we are forced to admit\ntime into our computational models.\n\nBefore we introduced\nassignment, all our programs were timeless, in the sense that any\nexpression that has a value always has the same value.\n\nIn contrast,\nrecall the example of modeling withdrawals from a bank account\nand returning the resulting balance,\nintroduced at the beginning of\nsection :\n\n```javascript\nwithdraw_example_3_4\n withdraw\n 75\n\nwithdraw(25);\n\n75\n```\n\n```javascript\nwithdraw_example_3_4_second\n withdraw\n withdraw_example_3_4\n 50\n\nwithdraw(25);\n\n50\n```\n\nHere successive evaluations of the same expression yield different\nvalues.\n\nThis behavior arises from the fact that the execution of\nassignments (in this case, assignments to the variable\n) delineates moments in time\nwhen values change.\n\nThe result of evaluating an expression depends not\nonly on the expression itself, but also on whether the evaluation occurs\nbefore or after these moments.\n\nBuilding models in terms of computational\nobjects with local state forces us to confront time as an essential concept\nin programming.\n\nWe can go further in structuring computational models to match our\nperception of the physical world.\n\nObjects in the world do not change\none at a time in sequence.\n\nRather we perceive them as acting\nconcurrently all at once.\n\nSo it is often natural to\nmodel systems as collections of\nthreads (sequences of computational steps)\nthat execute concurrently.", + "token_count": 288, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Concurrency: Time Is of the Essence", @@ -3140,8 +4390,8 @@ "chunk_id": "Modularity_Objects_and_State_Concurrency_Time_Is_of_the_Essence_1" }, { - "content": "So it is often natural to\nmodel systems as collections of\nthreads (sequences of computational steps)\nthat execute concurrently.\n\nJust as we can make our programs modular by\norganizing models in terms of objects with separate local state, it is\noften appropriate to divide computational models into parts that evolve\nseparately and concurrently.\n\nEven if the programs are to be executed on\na sequential computer, the practice of writing programs as if they were\nto be executed concurrently forces the programmer to avoid inessential\ntiming constraints and thus makes programs more modular.\n\nIn addition to making programs more modular, concurrent computation\ncan provide a speed advantage over sequential computation.\n\nSequential\ncomputers execute only one operation at a time, so the amount of time\nit takes to perform a task is proportional to the total number of\noperations performed.\n\nUnfortunately, the complexities introduced by assignment become even\nmore problematic in the presence of concurrency.\n\nThe fact of\nconcurrent execution, either because the world operates in parallel or\nbecause our computers do, entails additional complexity in our\nunderstanding of time.", - "token_count": 178, + "content": "So it is often natural to\nmodel systems as collections of\nthreads (sequences of computational steps)\nthat execute concurrently.\n\nEven if the programs are to be executed on\na sequential computer, the practice of writing programs as if they were\nto be executed concurrently forces the programmer to avoid inessential\ntiming constraints and thus makes programs more modular.\n\nIn addition to making programs more modular, concurrent computation\ncan provide a speed advantage over sequential computation.\n\nSequential\ncomputers execute only one operation at a time, so the amount of time\nit takes to perform a task is proportional to the total number of\noperations performed.\n\nHowever, if it is possible to decompose a problem into pieces that are\nrelatively independent and need to communicate only rarely, it may be\npossible to allocate pieces to separate computing processors,\nproducing a speed advantage proportional to the number of processors\navailable.\n\nUnfortunately, the complexities introduced by assignment become even\nmore problematic in the presence of concurrency.\n\nThe fact of\nconcurrent execution, either because the world operates in parallel or\nbecause our computers do, entails additional complexity in our\nunderstanding of time.", + "token_count": 187, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Concurrency: Time Is of the Essence", @@ -3160,7 +4410,7 @@ "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_1" }, { - "content": "More precisely, serialization\ncreates distinguished sets of\nfunctions\nsuch that only one execution of a\nfunction\nin each serialized set is permitted to happen at a time.\n\nIf some\nfunction\nin the set is being executed, then a\nthread\nthat attempts to execute any\nfunction\nin the set will be forced to wait\nuntil the first execution has finished.\n\nWe can use serialization to control access to shared variables.\n\nFor example, if we want to update a shared variable based on the\nprevious value of that variable, we put the access to the previous\nvalue of the variable and the assignment of the new value to the\nvariable in the same\nfunction.\n\nWe then ensure that no other\nfunction\nthat assigns to the variable can run concurrently with this\nfunction\nby serializing all of these\nfunctions\nwith the same serializer.\n\nThis guarantees that the value of the\nvariable cannot be changed between an access and the corresponding\nassignment.\n\nTo make the above mechanism more concrete, suppose that we have extended JavaScript to include a function called concurrent_execute:\n\n```javascript\nconcurrent_execute($f_{1}$, $f_{2}$, $\\ldots$, $f_{k}$)\n```\n\n```javascript\nEach $f$ must be a function of no arguments.\n The function concurrent_execute\n creates a separate thread for each\n $f$, which applies\n $f$ (to no arguments).\n```\n\nThese threads all run concurrently.\n\nAs an example of how this is used, consider\n\n```javascript\nconcurrent_execute_example\n 'all threads terminated'\n\nlet x = 10;\n\nconcurrent_execute(() => { x = x * x; },\n () => { x = x + 1; });\n```\n\n```javascript\nThis creates two concurrent\n\tthreads$T_1$, which sets\n\t$T_2$,\n\twhich increments $T_1$ and $T_2$:\n\n 101:\n\t $T_1$\n sets $T_2$ increments\n\n 121:\n\t $T_2$ increments\n\t $T_1$ sets\n\n 110:\n\t $T_2$ changes\n $T_1$\n\n\t accesses the value of\n x * x.\n\n 11:\n\t $T_2$ accesses\n\t $T_1$ sets $T_2$ sets\n\n 100:\n\t $T_1$ accesses\n\t $T_2$ sets\n\t $T_1$ sets\n```", + "content": "More precisely, serialization\ncreates distinguished sets of\nfunctions\nsuch that only one execution of a\nfunction\nin each serialized set is permitted to happen at a time.\n\nWe can use serialization to control access to shared variables.\n\nFor example, if we want to update a shared variable based on the\nprevious value of that variable, we put the access to the previous\nvalue of the variable and the assignment of the new value to the\nvariable in the same\nfunction.\n\nWe then ensure that no other\nfunction\nthat assigns to the variable can run concurrently with this\nfunction\nby serializing all of these\nfunctions\nwith the same serializer.\n\nThis guarantees that the value of the\nvariable cannot be changed between an access and the corresponding\nassignment.\n\nTo make the above mechanism more concrete, suppose that we have extended JavaScript to include a function called concurrent_execute:\n\n```javascript\nconcurrent_execute($f_{1}$, $f_{2}$, $\\ldots$, $f_{k}$)\n```\n\nEach $f$ must be a function of no arguments.\n\nThe function concurrent_execute creates a separate thread for each $f$, which applies $f$ (to no arguments).\n\nThese\nthreads\nall run concurrently.\n\nAs an example of how this is used, consider\n\n```javascript\nconcurrent_execute_example\n 'all threads terminated'\n\nlet x = 10;\n\nconcurrent_execute(() => { x = x * x; },\n () => { x = x + 1; });\n```\n\nThis creates two concurrent threads$T_1$, which sets to times , and $T_2$, which increments.\n\nAfter execution is complete, will be left with one of five possible values, depending on the interleaving of the events of $T_1$ and $T_2$: 101: $T_1$ sets to 100 and then $T_2$ increments to 101.\n\n121: $T_2$ increments to 11 and then $T_1$ sets to times.\n\n110: $T_2$ changes from 10 to 11 between the two times that $T_1$ accesses the value of during the evaluation of x * x.", "token_count": 302, "has_code": true, "chapter": "Modularity, Objects, and State", @@ -3170,8 +4420,8 @@ "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_2" }, { - "content": "As an example of how this is used, consider\n\nWe can constrain the concurrency by using serialized\nfunctions,\nwhich are created by serializers.\n\nSerializers are constructed by\nmake_serializer,\nwhose implementation is given below.\n\nA serializer takes a\nfunction\nas argument and returns a serialized\nfunction\nthat behaves like the original\nfunction.\n\nAll calls to a given serializer return serialized\nfunctions\nin the same set.\n\nThus, in contrast to the example above, executing\n\n```javascript\nserializer_example\n serializer\n 'all threads terminated'\n\nlet x = 10;\n\nconst s = make_serializer();\n\nconcurrent_execute(s(() => { x = x * x; }),\n s(() => { x = x + 1; }));\n```\n\ncan produce only two possible values for\n\n```javascript\n$T_1$ and\n $T_2$ cannot be interleaved.\n```\n\nHere is a version of the make_account function from section , where the deposits and withdrawals have been\n\n```javascript\nmake_account_serialize_attempt_example\n\nconst my_account = make_account(100);\n\nconcurrent_execute(\n () => {\n display(my_account(\"balance\"), \"T1 balance\");\n my_account(\"withdraw\")(50);\n display(my_account(\"balance\"), \"T1 balance\");\n my_account(\"deposit\")(100);\n },\n () => { display(my_account(\"balance\"), \"T2 balance\");\n my_account(\"withdraw\")(50);\n display(my_account(\"balance\"), \"T2 balance\");\n my_account(\"deposit\")(100);\n }\n);\n```\n\n```javascript\nmake_account_serialize_attempt\n serializer\n make_account_serialize_attempt_example\n\nfunction make_account(balance) {\n function withdraw(amount) {\n if (balance > amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n }\n function deposit(amount) {\n balance = balance + amount;\n return balance;\n }\n const protect = make_serializer();\n function dispatch(m) {\n return m === \"withdraw\"\n ? protect(withdraw)\n : m === \"deposit\"\n ? protect(deposit)\n : m === \"balance\"\n ? balance\n : error(m, \"unknown request -- make_account\");\n }\n return dispatch;\n}\n```\n\nWith this implementation, two\nthreads\ncannot be withdrawing from or\ndepositing into a single account concurrently.\n\nThis eliminates the source\nof the error illustrated in figure ,\nwhere Peter changes the account balance between the times when Paul accesses\nthe balance to compute the new value and when Paul actually performs the\nassignment.", - "token_count": 301, + "content": "110: $T_2$ changes from 10 to 11 between the two times that $T_1$ accesses the value of during the evaluation of x * x.\n\n100: $T_1$ accesses (twice), then $T_2$ sets to 11, then $T_1$ sets.\n\nWe can constrain the concurrency by using serialized\nfunctions,\nwhich are created by serializers.\n\nSerializers are constructed by\nmake_serializer,\nwhose implementation is given below.\n\nA serializer takes a\nfunction\nas argument and returns a serialized\nfunction\nthat behaves like the original\nfunction.\n\nAll calls to a given serializer return serialized\nfunctions\nin the same set.\n\nThus, in contrast to the example above, executing\n\n```javascript\nserializer_example\n serializer\n 'all threads terminated'\n\nlet x = 10;\n\nconst s = make_serializer();\n\nconcurrent_execute(s(() => { x = x * x; }),\n s(() => { x = x + 1; }));\n```\n\ncan produce only two possible values for\n, 101 or 121.\n\nThe other possibilities are eliminated, because the execution of\n$T_1$ and $T_2$ cannot be interleaved.\n\nHere is a version of the make_account function from section , where the deposits and withdrawals have been serialized:\n\n```javascript\nmake_account_serialize_attempt_example\n\nconst my_account = make_account(100);\n\nconcurrent_execute(\n () => {\n display(my_account(\"balance\"), \"T1 balance\");\n my_account(\"withdraw\")(50);\n display(my_account(\"balance\"), \"T1 balance\");\n my_account(\"deposit\")(100);\n },\n () => { display(my_account(\"balance\"), \"T2 balance\");\n my_account(\"withdraw\")(50);\n display(my_account(\"balance\"), \"T2 balance\");\n my_account(\"deposit\")(100);\n }\n);\n```\n\n```javascript\nmake_account_serialize_attempt\n serializer\n make_account_serialize_attempt_example\n\nfunction make_account(balance) {\n function withdraw(amount) {\n if (balance > amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n }\n function deposit(amount) {\n balance = balance + amount;\n return balance;\n }\n const protect = make_serializer();\n function dispatch(m) {\n return m === \"withdraw\"\n ? protect(withdraw)\n : m === \"deposit\"\n ? protect(deposit)\n : m === \"balance\"\n ? balance\n : error(m, \"unknown request -- make_account\");\n }\n return dispatch;\n}\n```\n\nWith this implementation, two\nthreads\ncannot be withdrawing from or\ndepositing into a single account concurrently.", + "token_count": 303, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Concurrency: Time Is of the Essence", @@ -3180,8 +4430,8 @@ "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_3" }, { - "content": "This eliminates the source\nof the error illustrated in figure ,\nwhere Peter changes the account balance between the times when Paul accesses\nthe balance to compute the new value and when Paul actually performs the\nassignment.\n\nOn the other hand, each account has its own serializer,\nso that deposits and withdrawals for different accounts can proceed\nconcurrently.\n\nSerializers provide a powerful abstraction that helps isolate the\ncomplexities of concurrent programs so that they can be dealt with\ncarefully and (hopefully) correctly.\n\nHowever, while using serializers\nis relatively straightforward when there is only a single shared\nresource (such as a single bank account), concurrent programming can\nbe treacherously difficult when there are multiple shared resources.\n\nTo illustrate one of the difficulties that can arise, suppose we wish to\n\n```javascript\nexchange_example\n\nconst a1 = make_account(100);\nconst a2 = make_account(200);\nconst a3 = make_account(300);\n\nconcurrent_execute(\n () => {\n display(a1(\"balance\"), \"Peter balance a1 before\");\n display(a2(\"balance\"), \"Peter balance a2 before\");\n exchange(a1, a2);\n display(a1(\"balance\"), \"Peter balance a1 after\");\n display(a2(\"balance\"), \"Peter balance a2 after\");\n },\n () => {\n display(a1(\"balance\"), \"Paul balance a1 before\");\n display(a3(\"balance\"), \"Paul balance a3 before\");\n exchange(a1, a3);\n display(a1(\"balance\"), \"Paul balance a1 after\");\n display(a3(\"balance\"), \"Paul balance a3 after\");\n }\n);\n```\n\n```javascript\nexchange\n make_account_serialize_attempt\n exchange_example\n\nfunction exchange(account1, account2) {\n const difference = account1(\"balance\") - account2(\"balance\");\n account1(\"withdraw\")(difference);\n account2(\"deposit\")(difference);\n}\n```\n\nThis\nfunction\nworks well when only a single\nthread\nis trying to do the exchange.\n\nSuppose, however, that Peter and Paul both\nhave access to accounts $a_1$ ,\n$a_2$ , and $a_3$ , and\nthat Peter exchanges $a_1$ and\n$a_2$ while Paul concurrently exchanges\n$a_1$ and $a_3$.", - "token_count": 262, + "content": "With this implementation, two\nthreads\ncannot be withdrawing from or\ndepositing into a single account concurrently.\n\nOn the other hand, each account has its own serializer,\nso that deposits and withdrawals for different accounts can proceed\nconcurrently.\n\nSerializers provide a powerful abstraction that helps isolate the\ncomplexities of concurrent programs so that they can be dealt with\ncarefully and (hopefully) correctly.\n\nHowever, while using serializers\nis relatively straightforward when there is only a single shared\nresource (such as a single bank account), concurrent programming can\nbe treacherously difficult when there are multiple shared resources.\n\nTo illustrate one of the difficulties that can arise, suppose we wish to\nswap the balances in two bank accounts.\n\nWe access each account to find\nthe balance, compute the difference between the balances, withdraw this\ndifference from one account, and deposit it in the other account.\n\nWe could implement this as\nfollows:\n\n```javascript\nexchange_example\n\nconst a1 = make_account(100);\nconst a2 = make_account(200);\nconst a3 = make_account(300);\n\nconcurrent_execute(\n () => {\n display(a1(\"balance\"), \"Peter balance a1 before\");\n display(a2(\"balance\"), \"Peter balance a2 before\");\n exchange(a1, a2);\n display(a1(\"balance\"), \"Peter balance a1 after\");\n display(a2(\"balance\"), \"Peter balance a2 after\");\n },\n () => {\n display(a1(\"balance\"), \"Paul balance a1 before\");\n display(a3(\"balance\"), \"Paul balance a3 before\");\n exchange(a1, a3);\n display(a1(\"balance\"), \"Paul balance a1 after\");\n display(a3(\"balance\"), \"Paul balance a3 after\");\n }\n);\n```\n\n```javascript\nexchange\n make_account_serialize_attempt\n exchange_example\n\nfunction exchange(account1, account2) {\n const difference = account1(\"balance\") - account2(\"balance\");\n account1(\"withdraw\")(difference);\n account2(\"deposit\")(difference);\n}\n```\n\nThis\nfunction\nworks well when only a single\nthread\nis trying to do the exchange.\n\nSuppose, however, that Peter and Paul both\nhave access to accounts $a_1$ ,\n$a_2$ , and $a_3$ , and\nthat Peter exchanges $a_1$ and\n$a_2$ while Paul concurrently exchanges\n$a_1$ and $a_3$.", + "token_count": 281, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Concurrency: Time Is of the Essence", @@ -3190,8 +4440,8 @@ "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_4" }, { - "content": "Suppose, however, that Peter and Paul both\nhave access to accounts $a_1$ ,\n$a_2$ , and $a_3$ , and\nthat Peter exchanges $a_1$ and\n$a_2$ while Paul concurrently exchanges\n$a_1$ and $a_3$.\n\nEven with account deposits and withdrawals\nserialized for individual accounts (as in the\nmake_account\nfunction\nshown above in this section), $a_1$ and\n$a_2$ , but then Paul might change the balance in\n$a_1$ before Peter is able to complete the\nexchange.\nfunction\nto lock out any other concurrent accesses to the accounts during the\nentire time of the exchange.\n\nOne way we can accomplish this is by using both accounts serializers\nto serialize the entire function.\n\nTo do this, we will arrange for access to an account s serializer.\n\nNote that we are deliberately breaking the modularity of the bank-account\nobject by exposing the serializer.\n\nThe following version of\nmake_@account\nis identical to the original version given in\nsection , except that a\nserializer is provided to protect the balance variable, and the serializer\nis exported via message passing:\n\n```javascript\nmake_account_and_serializer\n serializer\n 'all threads terminated'\n\nfunction make_account_and_serializer(balance) {\n function withdraw(amount) {\n if (balance > amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n }\n function deposit(amount) {\n balance = balance + amount;\n return balance;\n }\n const balance_serializer = make_serializer();\n return m => m === \"withdraw\"\n ? withdraw\n : m === \"deposit\"\n ? deposit\n : m === \"balance\"\n ? balance\n : m === \"serializer\"\n ? balance_serializer\n : error(m, \"unknown request -- make_account\");\n}\n```\n\nWe can use this to do serialized deposits and withdrawals.\n\nHowever,\nunlike our earlier serialized account, it is now the responsibility of\neach user of bank-account objects to explicitly manage the\nserialization, for example as\nfollows:\n\n```javascript\nmake_account_and_serializer_example\n\nconst my_account = make_account_and_serializer(100);\n\ndeposit(my_account, 200);\n\ndisplay(my_account(\"balance\"));\n```", - "token_count": 298, + "content": "Suppose, however, that Peter and Paul both\nhave access to accounts $a_1$ ,\n$a_2$ , and $a_3$ , and\nthat Peter exchanges $a_1$ and\n$a_2$ while Paul concurrently exchanges\n$a_1$ and $a_3$.\n\nFor example, Peter might compute the\ndifference in the balances for $a_1$ and\n$a_2$ , but then Paul might change the balance in\n$a_1$ before Peter is able to complete the\nexchange.\n\nFor correct behavior, we must arrange for the\nfunction\nto lock out any other concurrent accesses to the accounts during the\nentire time of the exchange.\n\nOne way we can accomplish this is by using both accounts serializers\nto serialize the entire\nfunction.\n\nTo do this, we will arrange for access to an account s serializer.\n\nNote that we are deliberately breaking the modularity of the bank-account\nobject by exposing the serializer.\n\nThe following version of\nmake_@account\nis identical to the original version given in\nsection , except that a\nserializer is provided to protect the balance variable, and the serializer\nis exported via message passing:\n\n```javascript\nmake_account_and_serializer\n serializer\n 'all threads terminated'\n\nfunction make_account_and_serializer(balance) {\n function withdraw(amount) {\n if (balance > amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n }\n function deposit(amount) {\n balance = balance + amount;\n return balance;\n }\n const balance_serializer = make_serializer();\n return m => m === \"withdraw\"\n ? withdraw\n : m === \"deposit\"\n ? deposit\n : m === \"balance\"\n ? balance\n : m === \"serializer\"\n ? balance_serializer\n : error(m, \"unknown request -- make_account\");\n}\n```\n\nWe can use this to do serialized deposits and withdrawals.\n\nHowever,\nunlike our earlier serialized account, it is now the responsibility of\neach user of bank-account objects to explicitly manage the\nserialization, for example as\nfollows:\n\n```javascript\nmake_account_and_serializer_example\n\nconst my_account = make_account_and_serializer(100);\n\ndeposit(my_account, 200);\n\ndisplay(my_account(\"balance\"));\n```", + "token_count": 297, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Concurrency: Time Is of the Essence", @@ -3200,8 +4450,8 @@ "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_5" }, { - "content": "However,\nunlike our earlier serialized account, it is now the responsibility of\neach user of bank-account objects to explicitly manage the\nserialization, for example as\nfollows:\n\n```javascript\nmake_account_and_serializer_usage\n make_account_and_serializer\n make_account_and_serializer_example\n\nfunction deposit(account, amount) {\n const s = account(\"serializer\");\n const d = account(\"deposit\");\n s(d(amount));\n}\n```\n\nExporting the serializer in this way gives us enough flexibility to\nimplement a serialized exchange program.\n\nWe simply serialize the original\nfunction\nwith the serializers for both accounts:\n\n```javascript\nserialized_exchange_example\n\nconst a1 = make_account_and_serializer(100);\nconst a2 = make_account_and_serializer(200);\nconst a3 = make_account_and_serializer(300);\n\nconcurrent_execute(\n () => {\n display(a1(\"balance\"), \"Peter balance a1 before\");\n display(a2(\"balance\"), \"Peter balance a2 before\");\n serialized_exchange(a1, a2);\n display(a1(\"balance\"), \"Peter balance a1 after\");\n display(a2(\"balance\"), \"Peter balance a2 after\");\n },\n () => {\n display(a1(\"balance\"), \"Paul balance a1 before\");\n display(a3(\"balance\"), \"Paul balance a3 before\");\n serialized_exchange(a1, a3);\n display(a1(\"balance\"), \"Paul balance a1 after\");\n display(a3(\"balance\"), \"Paul balance a3 after\");\n }\n);\n```\n\n```javascript\nserialized_exchange\n make_account_and_serializer\n exchange\n serialized_exchange_example\n\nfunction serialized_exchange(account1, account2) {\n const serializer1 = account1(\"serializer\");\n const serializer2 = account2(\"serializer\");\n serializer1(serializer2(exchange))(account1, account2);\n}\n```\n\nWe implement serializers in terms of a more primitive synchronization\nmechanism called a\nmutex.\n\nA mutex is an object that supports two\noperations the mutex can be\nacquired , and the mutex can be\nreleased.\n\nOnce a mutex has been acquired, no other acquire\noperations on that mutex may proceed until the mutex is\nreleased.\n\n```javascript\nfunction\n\tf,\n```\n\nthe serializer returns a\nfunction\nthat acquires the mutex, runs\nf,\nand then releases the mutex.\n\nThis ensures that only one of the\nfunctions\nproduced by the serializer can be running at once, which is\nprecisely the serialization property that we need to guarantee.", - "token_count": 266, + "content": "However,\nunlike our earlier serialized account, it is now the responsibility of\neach user of bank-account objects to explicitly manage the\nserialization, for example as\nfollows:\n\nExporting the serializer in this way gives us enough flexibility to\nimplement a serialized exchange program.\n\nWe simply serialize the original\nfunction\nwith the serializers for both accounts:\n\n```javascript\nserialized_exchange_example\n\nconst a1 = make_account_and_serializer(100);\nconst a2 = make_account_and_serializer(200);\nconst a3 = make_account_and_serializer(300);\n\nconcurrent_execute(\n () => {\n display(a1(\"balance\"), \"Peter balance a1 before\");\n display(a2(\"balance\"), \"Peter balance a2 before\");\n serialized_exchange(a1, a2);\n display(a1(\"balance\"), \"Peter balance a1 after\");\n display(a2(\"balance\"), \"Peter balance a2 after\");\n },\n () => {\n display(a1(\"balance\"), \"Paul balance a1 before\");\n display(a3(\"balance\"), \"Paul balance a3 before\");\n serialized_exchange(a1, a3);\n display(a1(\"balance\"), \"Paul balance a1 after\");\n display(a3(\"balance\"), \"Paul balance a3 after\");\n }\n);\n```\n\n```javascript\nserialized_exchange\n make_account_and_serializer\n exchange\n serialized_exchange_example\n\nfunction serialized_exchange(account1, account2) {\n const serializer1 = account1(\"serializer\");\n const serializer2 = account2(\"serializer\");\n serializer1(serializer2(exchange))(account1, account2);\n}\n```\n\nWe implement serializers in terms of a more primitive synchronization\nmechanism called a\nmutex.\n\nA mutex is an object that supports two\noperations the mutex can be\nacquired , and the mutex can be\nreleased.\n\nOnce a mutex has been acquired, no other acquire\noperations on that mutex may proceed until the mutex is\nreleased.\n\nIn our implementation, each serializer has an associated mutex.\n\nGiven a\nfunction f,\nthe serializer returns a\nfunction\nthat acquires the mutex, runs\nf,\nand then releases the mutex.\n\nThis ensures that only one of the\nfunctions\nproduced by the serializer can be running at once, which is\nprecisely the serialization property that we need to guarantee.\n\nTo apply serializers to functions that take an arbitrary number of arguments, we use JavaScript's rest parameter and spread syntax.\n\nThe... in front of the parameter args collects the rest (here all) of the arguments of any call of the function into a vector data structure.", + "token_count": 302, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Concurrency: Time Is of the Essence", @@ -3210,8 +4460,8 @@ "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_6" }, { - "content": "This ensures that only one of the\nfunctions\nproduced by the serializer can be running at once, which is\nprecisely the serialization property that we need to guarantee.\n\n```javascript\nTo apply serializers to functions that take an arbitrary number of arguments,\n\twe use JavaScript's rest parameter and spread syntax.\n\t... in front of the\n\tparameter args collects\n\tthe rest (here all) of the arguments of any call of the function\n\tinto a vector data structure.\n\tThe\n\t... in front of\n\targs in\n\tthe application\n\tf(...args)\n\tspreads the elements of\n\targs so that\n\tthey become separate arguments of\n\tf.\n```\n\n```javascript\nserializer\n mutex\n 'all threads terminated'\n\nfunction make_serializer() {\n const mutex = make_mutex();\n return f => {\n function serialized_f(...args) {\n mutex(\"acquire\");\n const val = f(...args);\n mutex(\"release\");\n return val;\n }\n return serialized_f;\n };\n}\n\nfunction make_serializer() {\n const mutex = make_mutex();\n return f => {\n function serialized_f() {\n mutex(\"acquire\");\n const val = f();\n mutex(\"release\");\n return val;\n }\n return serialized_f;\n };\n}\n```\n\nThe mutex is a mutable object (here we ll use a one-element list,\nwhich we ll refer to as a\ncell ) that can hold the value true or false.\n\nWhen the value is\nfalse, the mutex is available to be acquired.\n\nWhen the value is true, the\nmutex is unavailable, and any\nthread\nthat attempts to acquire the mutex must wait.\n\nOur mutex constructor\nmake_mutex\nbegins by initializing the cell contents to false.\n\nTo acquire the mutex,\nwe test the cell.\n\nIf the mutex is available, we set the cell contents to\ntrue and proceed.\n\nOtherwise, we wait in a loop, attempting to acquire over\nand over again, until we find that the mutex is available.", - "token_count": 277, + "content": "The... in front of the parameter args collects the rest (here all) of the arguments of any call of the function into a vector data structure.\n\n```javascript\nserializer\n mutex\n 'all threads terminated'\n\nfunction make_serializer() {\n const mutex = make_mutex();\n return f => {\n function serialized_f(...args) {\n mutex(\"acquire\");\n const val = f(...args);\n mutex(\"release\");\n return val;\n }\n return serialized_f;\n };\n}\n\nfunction make_serializer() {\n const mutex = make_mutex();\n return f => {\n function serialized_f() {\n mutex(\"acquire\");\n const val = f();\n mutex(\"release\");\n return val;\n }\n return serialized_f;\n };\n}\n```\n\nThe mutex is a mutable object (here we ll use a one-element list,\nwhich we ll refer to as a\ncell ) that can hold the value true or false.\n\nWhen the value is\nfalse, the mutex is available to be acquired.\n\nWhen the value is true, the\nmutex is unavailable, and any\nthread\nthat attempts to acquire the mutex must wait.\n\nOur mutex constructor\nmake_mutex\nbegins by initializing the cell contents to false.\n\nTo acquire the mutex,\nwe test the cell.\n\nIf the mutex is available, we set the cell contents to\ntrue and proceed.\n\nOtherwise, we wait in a loop, attempting to acquire over\nand over again, until we find that the mutex is available.\n\nTo release the mutex, we set the cell contents to false.\n\n```javascript\nmutex\n 'all threads terminated'\n\nfunction make_mutex() {\n const cell = list(false);\n function the_mutex(m) {\n return m === \"acquire\"\n ? test_and_set(cell)\n ? the_mutex(\"acquire\") // retry\n : true\n : m === \"release\"\n ? clear(cell)\n : error(m, \"unknown request -- mutex\");\n }\n return the_mutex;\n}\nfunction clear(cell) {\n set_head(cell, false);\n}\n```\n\nThe function test_and_set\ntests the cell and returns the result of the test.\n\nIn addition, if the\ntest was false,\ntest_and_set\nsets the cell contents to true before returning false.\n\nWe can express this\nbehavior as the following\nfunction:", + "token_count": 304, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Concurrency: Time Is of the Essence", @@ -3220,9 +4470,9 @@ "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_7" }, { - "content": "Otherwise, we wait in a loop, attempting to acquire over\nand over again, until we find that the mutex is available.\n\n```javascript\nmutex\n 'all threads terminated'\n\nfunction make_mutex() {\n const cell = list(false);\n function the_mutex(m) {\n return m === \"acquire\"\n ? test_and_set(cell)\n ? the_mutex(\"acquire\") // retry\n : true\n : m === \"release\"\n ? clear(cell)\n : error(m, \"unknown request -- mutex\");\n }\n return the_mutex;\n}\nfunction clear(cell) {\n set_head(cell, false);\n}\n```\n\nThe function test_and_set\ntests the cell and returns the result of the test.\n\nIn addition, if the\ntest was false,\ntest_and_set\nsets the cell contents to true before returning false.\n\nWe can express this\nbehavior as the following\nfunction:\n\n```javascript\ntest_and_set\n 'all threads terminated'\n\nfunction test_and_set(cell) {\n if (head(cell)) {\n return true;\n } else {\n set_head(cell, true);\n return false;\n }\n}\n```\n\nHowever, this implementation of\ntest_and_set\ndoes not suffice as it stands.\n\nThere is a crucial subtlety here, which is\nthe essential place where concurrency control enters the system: The\ntest_and_set\noperation must be performed\natomically.\n\nThat is, we must guarantee that, once a\nthread\nhas tested the cell and found it to be false, the cell contents will\nactually be set to true before any other\nthread\ncan test the cell.\n\nIf we do not make this guarantee, then the mutex can\nfail in a way similar to the bank-account failure in\nfigure.\n\n(See\nexercise.)\n\nThe actual implementation of\ntest_and_set\ndepends on the details of how our system runs concurrent\nthreads.\n\nFor example, we might be executing concurrent\nthreads\non a sequential processor using a\nthreads,\npermitting each\nthread\nto run for a short time before interrupting it\nand moving on to the next\nthread.\n\nIn that case,\ntest_and_set\ncan work by disabling time slicing during the testing and\nsetting.", - "token_count": 293, - "has_code": true, + "content": "We can express this\nbehavior as the following\nfunction:\n\nHowever, this implementation of\ntest_and_set\ndoes not suffice as it stands.\n\nThere is a crucial subtlety here, which is\nthe essential place where concurrency control enters the system: The\ntest_and_set\noperation must be performed\natomically.\n\nThat is, we must guarantee that, once a\nthread\nhas tested the cell and found it to be false, the cell contents will\nactually be set to true before any other\nthread\ncan test the cell.\n\nIf we do not make this guarantee, then the mutex can\nfail in a way similar to the bank-account failure in\nfigure.\n\n(See\nexercise.)\n\nThe actual implementation of\ntest_and_set\ndepends on the details of how our system runs concurrent\nthreads.\n\nFor example, we might be executing concurrent\nthreads\non a sequential processor using a\ntime-slicing mechanism that cycles through the\nthreads,\npermitting each\nthread\nto run for a short time before interrupting it\nand moving on to the next\nthread.\n\nIn that case,\ntest_and_set\ncan work by disabling time slicing during the testing and\nsetting.\n\nAlternatively, multiprocessing computers provide instructions that\nsupport atomic operations directly in hardware.\n\nNow that we have seen how to implement serializers, we can see\nthat account exchanging still has a problem, even with the\nserialized_exchange\nfunction\nabove.\n\nImagine that Peter attempts to exchange $a_1$\nwith $a_2$ while Paul concurrently attempts to\nexchange $a_2$ with\n$a_1$.\n\nSuppose that Peter s\nthread\nreaches the point where it has entered a serialized\nfunction\nprotecting $a_1$ and, just after that,\nPaul s\nthread\nenters a serialized\nfunction\nprotecting $a_2$.\n\nNow Peter cannot proceed (to\nenter a serialized\nfunction\nprotecting $a_2$ ) until Paul exits the serialized\nfunction\nprotecting $a_2$.\n\nSimilarly, Paul cannot proceed\nuntil Peter exits the serialized\nfunction\nprotecting $a_1$.", + "token_count": 292, + "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Concurrency: Time Is of the Essence", "subsection": "Mechanisms for Controlling Concurrency", @@ -3230,8 +4480,8 @@ "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_8" }, { - "content": "In that case,\ntest_and_set\ncan work by disabling time slicing during the testing and\nsetting.\n\nAlternatively, multiprocessing computers provide instructions that\nsupport atomic operations directly in hardware.\n\nNow that we have seen how to implement serializers, we can see\nthat account exchanging still has a problem, even with the\nserialized_exchange\nfunction\nabove.\n\nImagine that Peter attempts to exchange $a_1$\nwith $a_2$ while Paul concurrently attempts to\nexchange $a_2$ with\n$a_1$.\n\nSuppose that Peter s\nthread\nreaches the point where it has entered a serialized\nfunction\nprotecting $a_1$ and, just after that,\nPaul s\nthread\nenters a serialized\nfunction\nprotecting $a_2$.\n\nNow Peter cannot proceed (to\nenter a serialized\nfunction\nprotecting $a_2$ ) until Paul exits the serialized\nfunction\nprotecting $a_2$.\n\nSimilarly, Paul cannot proceed\nuntil Peter exits the serialized\nfunction\nprotecting $a_1$.\n\nEach\nthread\nis stalled forever, waiting for the other.\n\nThis situation is called a\ndeadlock.\n\nDeadlock is always a danger in systems that provide\nconcurrent access to multiple shared resources.\n\nOne way to avoid the\nserialized_exchange\nso that a\nthread\nwill always attempt to enter a\nfunction\nprotecting the lowest-numbered account first.\n\nAlthough this method works\nwell for the exchange problem, there are other situations that require more\nsophisticated deadlock-avoidance techniques, or where deadlock cannot\nbe avoided at all.\n\n(See exercises\nand.)\n\nWe ve seen how programming concurrent systems requires controlling\nthe ordering of events when different\nthreads\naccess shared state, and we ve seen how to achieve this control\nthrough judicious use of serializers.\n\nBut the problems of concurrency\nlie deeper than this, because, from a fundamental point of view, it s\nnot always clear what is meant by shared state.\n\nMechanisms such as\ntest_and_set\nrequire\nthreads\nto examine a global shared flag at arbitrary times.", - "token_count": 289, + "content": "Similarly, Paul cannot proceed\nuntil Peter exits the serialized\nfunction\nprotecting $a_1$.\n\nThis situation is called a\ndeadlock.\n\nDeadlock is always a danger in systems that provide\nconcurrent access to multiple shared resources.\n\nOne way to avoid the\ndeadlock in this situation is to give each account a\nunique identification number and rewrite\nserialized_exchange\nso that a\nthread\nwill always attempt to enter a\nfunction\nprotecting the lowest-numbered account first.\n\nAlthough this method works\nwell for the exchange problem, there are other situations that require more\nsophisticated deadlock-avoidance techniques, or where deadlock cannot\nbe avoided at all.\n\n(See exercises\nand.)\n\nWe ve seen how programming concurrent systems requires controlling\nthe ordering of events when different\nthreads\naccess shared state, and we ve seen how to achieve this control\nthrough judicious use of serializers.\n\nBut the problems of concurrency\nlie deeper than this, because, from a fundamental point of view, it s\nnot always clear what is meant by shared state.\n\nMechanisms such as\ntest_and_set\nrequire\nthreads\nto examine a global shared flag at arbitrary times.\n\nThis is problematic\nand inefficient to implement in modern high-speed processors, where\ndue to optimization techniques such as pipelining and cached memory,\nthe contents of memory may not be in a consistent state at every instant.\n\nIn\nsome\nmultiprocessing systems, therefore, the serializer paradigm\nis being supplanted by\nother\napproaches to concurrency\ncontrol.\n\nThe problematic aspects of shared state also arise in large, distributed\nsystems.\n\nFor instance, imagine a distributed banking system where\nindividual branch banks maintain local values for bank balances and\nperiodically compare these with values maintained by other branches.\n\nIn\nsuch a system the value of the account balance would be\nundetermined, except right after synchronization.", + "token_count": 284, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Concurrency: Time Is of the Essence", @@ -3240,8 +4490,8 @@ "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_9" }, { - "content": "Mechanisms such as\ntest_and_set\nrequire\nthreads\nto examine a global shared flag at arbitrary times.\n\nThis is problematic\nand inefficient to implement in modern high-speed processors, where\ndue to optimization techniques such as pipelining and cached memory,\nthe contents of memory may not be in a consistent state at every instant.\n\nIn\nsome\nmultiprocessing systems, therefore, the serializer paradigm\nis being supplanted by\nother\napproaches to concurrency\ncontrol.\n\nThe problematic aspects of shared state also arise in large, distributed\nsystems.\n\nFor instance, imagine a distributed banking system where\nindividual branch banks maintain local values for bank balances and\nperiodically compare these with values maintained by other branches.\n\nIn\nsuch a system the value of the account balance would be\nundetermined, except right after synchronization.\n\nIf Peter deposits money\nin an account he holds jointly with Paul, when should we say that the\naccount balance has changed when the balance in the local branch\nchanges, or not until after the synchronization?\n\nAnd if Paul accesses the\naccount from a different branch, what are the reasonable constraints to\nplace on the banking system such that the behavior is\ncorrect ?\n\nThe only thing that might matter for correctness\nis the behavior observed by Peter and Paul individually and the\nstate of the account immediately after synchronization.\n\nQuestions about the real account balance or the order of\nevents between synchronizations may be irrelevant or\nmeaningless.\n\nThe basic phenomenon here is that synchronizing different threads, establishing shared state, or imposing an order on events requires communication among the threads.", - "token_count": 255, + "content": "In\nsuch a system the value of the account balance would be\nundetermined, except right after synchronization.\n\nAnd if Paul accesses the\naccount from a different branch, what are the reasonable constraints to\nplace on the banking system such that the behavior is\ncorrect ?\n\nThe only thing that might matter for correctness\nis the behavior observed by Peter and Paul individually and the\nstate of the account immediately after synchronization.\n\nQuestions about the real account balance or the order of\nevents between synchronizations may be irrelevant or\nmeaningless.\n\nThe basic phenomenon here is that synchronizing different\nthreads,\nestablishing shared state, or imposing an order on events requires\ncommunication among the\nthreads.\n\nIn essence, any notion of time in concurrency control must be intimately\ntied to communication.\n\nIt is intriguing that a similar\nconnection between time and communication also arises in the\nTheory of Relativity, where the speed of light (the fastest signal that can\nbe used to synchronize events) is a fundamental constant relating time and\nspace.\n\nThe complexities we encounter in dealing with time and state in our\ncomputational models may in fact mirror a fundamental complexity of\nthe physical universe.", + "token_count": 193, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Concurrency: Time Is of the Essence", @@ -3250,8 +4500,8 @@ "chunk_id": "Modularity_Objects_and_State_Mechanisms_for_Controlling_Concurrency_10" }, { - "content": "On the surface, time seems straightforward.\n\nIt\nis an ordering imposed on events. $A$ and\n$B$ ,\neither $A$ occurs before\n$B$ ,\n$A$ and\n$B$ are simultaneous, or\n$A$ occurs after\n$B$.\n\nFor instance,\nreturning to the bank account example, suppose that Peter withdraws\n10 and Paul withdraws 25 from a\n100, leaving 65 in the account.\n\nDepending on the\norder of the two withdrawals, the sequence of balances in the account is\neither $\\$100 \\rightarrow \\$90 \\rightarrow\\$65$\nor $\\$100 \\rightarrow \\$75 \\rightarrow\\$65$.\n\nIn a computer implementation of the banking system, this changing\nsequence of balances could be modeled by successive assignments to\na variable\n\nIn complex situations, however, such a view can be problematic.\n\nSuppose that Peter and Paul, and other people besides, are\naccessing the same bank account through a network of banking machines\ndistributed all over the world.\n\nThe actual sequence of balances in\nthe account will depend critically on the detailed timing of the\naccesses and the details of the communication among the machines.\n\nThis threads sharing a common variable thread specified by the function given in section :\n\n```javascript\nwithdraw_concurrency\n balance_concurrency\n withdraw_example_concurrency\n 75\n\nfunction withdraw(amount) {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n}\n```\n\n```javascript\nbalance_concurrency\n\nlet balance = 100;\n```\n\n```javascript\nwithdraw_example_concurrency\n\nwithdraw(25); // output: 75\n```\n\nIf the two threads operate independently, then Peter might test the balance and attempt to withdraw a legitimate amount. s test.\n\nThings can be worse still.\n\nConsider the\nstatement\n\n```javascript\nbalance = balance - amount;\n```\n\nexecuted as part of each withdrawal process.\n\nThis consists of three\nsteps: (1) accessing the value of the s withdrawals execute this statement concurrently, then the\ntwo withdrawals might interleave the order in which they access", - "token_count": 299, + "content": "On the surface, time seems straightforward.\n\nIt\nis an ordering imposed on events.\n\nFor any events $A$ and\n$B$ ,\neither $A$ occurs before\n$B$ ,\n$A$ and\n$B$ are simultaneous, or\n$A$ occurs after\n$B$.\n\nFor instance,\nreturning to the bank account example, suppose that Peter withdraws\n10 and Paul withdraws 25 from a\njoint account that initially\ncontains 100, leaving 65 in the account.\n\nDepending on the\norder of the two withdrawals, the sequence of balances in the account is\neither $\\$100 \\rightarrow \\$90 \\rightarrow\\$65$\nor $\\$100 \\rightarrow \\$75 \\rightarrow\\$65$.\n\nIn a computer implementation of the banking system, this changing\nsequence of balances could be modeled by successive assignments to\na variable.\n\nIn complex situations, however, such a view can be problematic.\n\nSuppose that Peter and Paul, and other people besides, are\naccessing the same bank account through a network of banking machines\ndistributed all over the world.\n\nThe actual sequence of balances in\nthe account will depend critically on the detailed timing of the\naccesses and the details of the communication among the machines.\n\nThis\nindeterminacy in the order of events can pose serious problems in\nthe design of concurrent systems.\n\nFor instance, suppose that the\nwithdrawals made by Peter and Paul are implemented as two separate\nthreads\nsharing a common variable , each\nthread\nspecified by the\nfunction\ngiven in section :\n\n```javascript\nwithdraw_concurrency\n balance_concurrency\n withdraw_example_concurrency\n 75\n\nfunction withdraw(amount) {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"Insufficient funds\";\n }\n}\n```\n\n```javascript\nbalance_concurrency\n\nlet balance = 100;\n```\n\n```javascript\nwithdraw_example_concurrency\n\nwithdraw(25); // output: 75\n```\n\nIf the two\nthreads\noperate independently, then Peter might test the\nbalance and attempt to withdraw a legitimate amount.", + "token_count": 288, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Concurrency: Time Is of the Essence", @@ -3260,9 +4510,9 @@ "chunk_id": "Modularity_Objects_and_State_The_Nature_of_Time_in_Concurrent_Systems_1" }, { - "content": "This consists of three\nsteps: (1) accessing the value of the s withdrawals execute this statement concurrently, then the\ntwo withdrawals might interleave the order in which they access\n\nThe timing diagram in\nfigure\ndepicts\nan order of events where s assignment of 75 to\n100.\n\nAfterwards, Peter has 10, Paul has\n25, and the bank has 75.\n\nThe general phenomenon illustrated\nhere is that several\nthreads\nmay\nthread\nmay be trying to manipulate the shared state at the same\ntime.\n\nFor the bank account example, during each transaction, each\ncustomer should be able to act as if the other customers did not\nexist.\n\nWhen\ncustomers change\nthe balance in a way that depends on\nthe balance,\nthey\nmust be able to assume that, just before the moment of\nchange, the balance is still what\nthey\nthought it was.\n\nThe above example typifies the subtle bugs that can creep into\nconcurrent programs.\n\nThe root of this complexity lies in the\nassignments to variables that are shared among the different\nthreads.\n\nWe already know that we must be careful in writing programs that use\nassignment,\nbecause the results of a computation depend on the order in which the\nassignments occur. threads\nwe must be especially careful about\nassignments, because we may not be able to control the order of the\nassignments made by the different\nthreads.\n\nIf several such changes\nmight be made concurrently (as with two depositors accessing a joint\naccount) we need some way to ensure that our system behaves correctly.\n\nFor example, in the case of withdrawals from a joint bank account, we\nmust ensure that money is conserved.\n\nTo make concurrent programs behave correctly, we may have to\nplace some restrictions on concurrent execution.", - "token_count": 287, - "has_code": false, + "content": "If the two\nthreads\noperate independently, then Peter might test the\nbalance and attempt to withdraw a legitimate amount.\n\nThings can be worse still.\n\nConsider the\nstatement\n\n```javascript\nbalance = balance - amount;\n```\n\nexecuted as part of each withdrawal process.\n\nThis consists of three\nsteps: (1) accessing the value of the\nvariable; (2) computing the new balance; (3) setting\nto this new value.\n\nIf Peter and\nPaul s withdrawals execute this statement concurrently, then the\ntwo withdrawals might interleave the order in which they access\nand set it to the new value.\n\nThe timing diagram in\nfigure\ndepicts\nan order of events where starts at 100,\nPeter withdraws 10, Paul withdraws 25, and yet the final value of\nis 75.\n\nAs shown in the diagram,\nthe reason for this anomaly is that Paul s assignment of 75 to\nis made under the assumption that\nthe value of to be decremented is 100.\n\nThat assumption, however, became invalid when Peter changed\nto 90.\n\nThis is a catastrophic\nfailure for the banking system, because the total amount of money in the\nsystem is not conserved.\n\nBefore the transactions, the total amount of\nmoney was 100.\n\nAfterwards, Peter has 10, Paul has\n25, and the bank has 75.\n\nThe general phenomenon illustrated\nhere is that several\nthreads\nmay\nshare a common state variable.\n\nWhat makes this complicated is that more than one\nthread\nmay be trying to manipulate the shared state at the same\ntime.\n\nFor the bank account example, during each transaction, each\ncustomer should be able to act as if the other customers did not\nexist.\n\nWhen\ncustomers change\nthe balance in a way that depends on\nthe balance,\nthey\nmust be able to assume that, just before the moment of\nchange, the balance is still what\nthey\nthought it was.", + "token_count": 301, + "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Concurrency: Time Is of the Essence", "subsection": "The Nature of Time in Concurrent Systems", @@ -3270,8 +4520,8 @@ "chunk_id": "Modularity_Objects_and_State_The_Nature_of_Time_in_Concurrent_Systems_2" }, { - "content": "To make concurrent programs behave correctly, we may have to\nplace some restrictions on concurrent execution.\n\nOne possible restriction on concurrency would stipulate that no two\noperations that change any shared state variables can occur at the same\ntime.\n\nThis is an extremely stringent requirement.\n\nFor distributed banking,\nit would require the system designer to ensure that only one transaction\ncould proceed at a time.\n\nThis would be both inefficient and overly\nconservative.\n\nFigure shows\nPeter and Paul sharing a bank account, where Paul has a private account\nas well.\n\nThe diagram illustrates two withdrawals from the shared account\n(one by Peter and one by Paul) and a deposit to Paul s private\naccount. s deposit and\nwithdrawal must not be concurrent (since both access and update the amount\nin Paul s wallet).\n\nBut there should be no problem permitting\nPaul s deposit to his private account to proceed concurrently with\nPeter s withdrawal from the shared account.\n\nConcurrent deposits and withdrawals from a\njoint account in Bank1 and a private account in Bank2.\n\nA less stringent restriction on concurrency would ensure that a\nconcurrent system produces the same result as if the\nthreads\nhad run sequentially in some order.\n\nThere are two important aspects to this\nrequirement.\n\nFirst, it does not require the\nthreads\nto actually run sequentially, but only to produce results that are the same\nas if they had run sequentially.\n\nFor the example in\nfigure , the designer of the\nbank account system can safely allow Paul s deposit and Peter s\nwithdrawal to happen concurrently, because the net result will be the same as\nif the two operations had happened sequentially.", - "token_count": 276, + "content": "When\ncustomers change\nthe balance in a way that depends on\nthe balance,\nthey\nmust be able to assume that, just before the moment of\nchange, the balance is still what\nthey\nthought it was.\n\nThe root of this complexity lies in the\nassignments to variables that are shared among the different\nthreads.\n\nWe already know that we must be careful in writing programs that use\nassignment,\nbecause the results of a computation depend on the order in which the\nassignments occur.\n\nWith concurrent\nthreads\nwe must be especially careful about\nassignments, because we may not be able to control the order of the\nassignments made by the different\nthreads.\n\nIf several such changes\nmight be made concurrently (as with two depositors accessing a joint\naccount) we need some way to ensure that our system behaves correctly.\n\nFor example, in the case of withdrawals from a joint bank account, we\nmust ensure that money is conserved.\n\nTo make concurrent programs behave correctly, we may have to\nplace some restrictions on concurrent execution.\n\nOne possible restriction on concurrency would stipulate that no two\noperations that change any shared state variables can occur at the same\ntime.\n\nThis is an extremely stringent requirement.\n\nFor distributed banking,\nit would require the system designer to ensure that only one transaction\ncould proceed at a time.\n\nThis would be both inefficient and overly\nconservative.\n\nFigure shows\nPeter and Paul sharing a bank account, where Paul has a private account\nas well.\n\nThe diagram illustrates two withdrawals from the shared account\n(one by Peter and one by Paul) and a deposit to Paul s private\naccount.", + "token_count": 270, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Concurrency: Time Is of the Essence", @@ -3280,8 +4530,8 @@ "chunk_id": "Modularity_Objects_and_State_The_Nature_of_Time_in_Concurrent_Systems_3" }, { - "content": "For the example in\nfigure , the designer of the\nbank account system can safely allow Paul s deposit and Peter s\nwithdrawal to happen concurrently, because the net result will be the same as\nif the two operations had happened sequentially.\n\nSecond, there may be more\nthan one possible correct result produced by a concurrent\nprogram, because we require only that the result be the same as for\nsome sequential order.\n\nFor example, suppose that Peter and\nPaul s joint account starts out with 100, and Peter deposits\n40 while Paul concurrently withdraws half the money in the account.\n\nThen sequential execution could result in the account balance being either\n70 or 90 (see\nexercise ).\n\nThere are still weaker requirements for correct execution of concurrent\nprograms.\n\nA program for simulating\nthreads,\neach one representing a small volume of space, that update their values\nconcurrently.\n\nEach\nthread\nrepeatedly changes its value to the average of its own value and its\nneighbors values.\n\nThis algorithm converges to the right answer\nindependent of the order in which the operations are done; there is no\nneed for any restrictions on concurrent use of the shared values.", - "token_count": 194, + "content": "The diagram illustrates two withdrawals from the shared account\n(one by Peter and one by Paul) and a deposit to Paul s private\naccount.\n\nBut there should be no problem permitting\nPaul s deposit to his private account to proceed concurrently with\nPeter s withdrawal from the shared account.\n\nConcurrent deposits and withdrawals from a\njoint account in Bank1 and a private account in Bank2.\n\nA less stringent restriction on concurrency would ensure that a\nconcurrent system produces the same result as if the\nthreads\nhad run sequentially in some order.\n\nThere are two important aspects to this\nrequirement.\n\nFirst, it does not require the\nthreads\nto actually run sequentially, but only to produce results that are the same\nas if they had run sequentially.\n\nFor the example in\nfigure , the designer of the\nbank account system can safely allow Paul s deposit and Peter s\nwithdrawal to happen concurrently, because the net result will be the same as\nif the two operations had happened sequentially.\n\nSecond, there may be more\nthan one possible correct result produced by a concurrent\nprogram, because we require only that the result be the same as for\nsome sequential order.\n\nFor example, suppose that Peter and\nPaul s joint account starts out with 100, and Peter deposits\n40 while Paul concurrently withdraws half the money in the account.\n\nThen sequential execution could result in the account balance being either\n70 or 90 (see\nexercise ).\n\nThere are still weaker requirements for correct execution of concurrent\nprograms.\n\nA program for simulating\ndiffusion (say, the flow of heat in an object) might consist of a large\nnumber of\nthreads,\neach one representing a small volume of space, that update their values\nconcurrently.", + "token_count": 287, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Concurrency: Time Is of the Essence", @@ -3289,6 +4539,16 @@ "chunk_index": 4, "chunk_id": "Modularity_Objects_and_State_The_Nature_of_Time_in_Concurrent_Systems_4" }, + { + "content": "A program for simulating\ndiffusion (say, the flow of heat in an object) might consist of a large\nnumber of\nthreads,\neach one representing a small volume of space, that update their values\nconcurrently.\n\nThis algorithm converges to the right answer\nindependent of the order in which the operations are done; there is no\nneed for any restrictions on concurrent use of the shared values.", + "token_count": 65, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Concurrency: Time Is of the Essence", + "subsection": "The Nature of Time in Concurrent Systems", + "chunk_index": 5, + "chunk_id": "Modularity_Objects_and_State_The_Nature_of_Time_in_Concurrent_Systems_5" + }, { "content": "Chapter dealt with compound data as a means for constructing\ncomputational objects that have several parts, in order to model\nreal-world objects that have several aspects.\n\nIn that chapter we\nintroduced the discipline of data abstraction, according to which data\nstructures are specified in terms of constructors, which create data\nobjects, and selectors, which access the parts of compound data\nobjects.\n\nBut we now know that there is another aspect of data that\nchapter did not address.\n\nThe desire to model systems composed of\nobjects that have changing state leads us to the need to modify\ncompound data objects, as well as to construct and select from them.\n\nIn order to model compound objects with changing state, we will design\ndata abstractions to include, in addition to selectors and\nconstructors, operations called\nmutators , which modify data\nobjects.\n\nFor instance, modeling a banking system requires us to\nchange account balances.\n\nThus, a data structure for representing bank\naccounts might admit an operation\n\n```javascript\nset_balance(account, new-value)\n```\n\nthat changes the balance of the designated account to the designated\nnew value.\n\nData objects for which mutators are defined are known as\nmutable data objects.\n\nChapter introduced pairs as a general-purpose glue\nfor synthesizing compound data.\n\nWe begin this section by defining basic\nmutators for pairs, so that pairs can serve as building blocks for\nconstructing mutable data objects.\n\nThese mutators greatly enhance the\nrepresentational power of pairs, enabling us to build data structures\nother than the sequences and trees that we worked with in\nsection.\n\nWe also present some\nexamples of simulations in which complex systems are modeled as collections\nof objects with local state.", "token_count": 273, @@ -3300,8 +4560,8 @@ "chunk_id": "Modularity_Objects_and_State_Modeling_with_Mutable_Data_1" }, { - "content": "Designing complex digital systems, such as computers, is an important\nengineering activity.\n\nDigital systems are constructed by\ninterconnecting simple elements.\n\nAlthough the behavior of these\nindividual elements is simple, networks of them can have very complex\nbehavior.\n\nComputer simulation of proposed circuit designs is an\nimportant tool used by digital systems engineers.\n\nIn this section we\ndesign a system for performing digital logic simulations.\n\nThis system\ntypifies a kind of program called an\nevent-driven simulation , in\nwhich actions ( events ) trigger further events that happen\nat a later time, which in turn trigger more events, and so on.\n\nOur computational model of a circuit will be composed of objects that\ncorrespond to the elementary components from which the circuit is\nconstructed.\n\nThere are\nwires , which carry\ndigital signals.\n\nA digital signal may at any moment have only one of two\npossible values,\n0 and 1.\n\nThere are also various types of digital\nfunction boxes , which connect wires carrying input signals to other output\nwires.\n\nSuch boxes produce output signals computed from their input\nsignals.\n\nThe output signal is\ninverter is a\nprimitive function box that inverts its input.\n\nIf the\ninput signal to an inverter changes to 0, then one inverter-delay\nlater the inverter will change its output signal to 1.\n\nIf the input\nsignal to an inverter changes to 1, then one inverter-delay later\nthe inverter will change its output signal to 0.\n\nWe draw an inverter\nsymbolically as in figure.\n\nAn\nand-gate ,\nalso shown in figure , is a primitive\nfunction box with two inputs and one output.\n\nIt drives its output signal\nto a value that is the\nlogical and of the inputs.", - "token_count": 281, + "content": "Designing complex digital systems, such as computers, is an important\nengineering activity.\n\nDigital systems are constructed by\ninterconnecting simple elements.\n\nAlthough the behavior of these\nindividual elements is simple, networks of them can have very complex\nbehavior.\n\nComputer simulation of proposed circuit designs is an\nimportant tool used by digital systems engineers.\n\nIn this section we\ndesign a system for performing digital logic simulations.\n\nThis system\ntypifies a kind of program called an\nevent-driven simulation , in\nwhich actions ( events ) trigger further events that happen\nat a later time, which in turn trigger more events, and so on.\n\nOur computational model of a circuit will be composed of objects that\ncorrespond to the elementary components from which the circuit is\nconstructed.\n\nThere are\nwires , which carry\ndigital signals.\n\nA digital signal may at any moment have only one of two\npossible values,\n0 and 1.\n\nThere are also various types of digital\nfunction boxes , which connect wires carrying input signals to other output\nwires.\n\nSuch boxes produce output signals computed from their input\nsignals.\n\nThe output signal is\ndelayed by a time that depends on the\ntype of the function box.\n\nFor example, an\ninverter is a\nprimitive function box that inverts its input.\n\nIf the\ninput signal to an inverter changes to 0, then one inverter-delay\nlater the inverter will change its output signal to 1.\n\nIf the input\nsignal to an inverter changes to 1, then one inverter-delay later\nthe inverter will change its output signal to 0.\n\nWe draw an inverter\nsymbolically as in figure.\n\nAn\nand-gate ,\nalso shown in figure , is a primitive\nfunction box with two inputs and one output.\n\nIt drives its output signal\nto a value that is the\nlogical and of the inputs.", + "token_count": 297, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3310,9 +4570,9 @@ "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_1" }, { - "content": "It drives its output signal\nto a value that is the\nlogical and of the inputs.\n\nThat is, if both\nof its input signals become 1, then one and-gate-delay time\nlater the and-gate will force its output signal to be 1; otherwise the\noutput will be 0.\n\nAn\nor-gate is a similar two-input primitive function\nbox that drives its output signal to a value that is the\nlogical or of the inputs.\n\nThat is, the output will become 1 if at least one\nof the input signals is 1; otherwise the output will become 0.\n\nWe can connect primitive functions together to construct more complex\nfunctions.\n\nTo accomplish this we wire the outputs of some\nfunction boxes to the inputs of other function boxes.\n\nFor example,\nthe\nhalf-adder circuit shown in\nfigure consists of an\nor-gate, two and-gates, and an inverter.\n\nIt takes two input signals,\n$A$ and $B$ , and has\ntwo output signals, $S$ and $C$.\n$S$ will become 1\nwhenever precisely one of $A$ and $B$\nis 1, and $C$ will become 1 whenever\n$A$ and $B$ are both 1.\n\nWe can see\nfrom the figure that, because of the\ndelays involved, the outputs may be generated at different times.\n\nMany of the difficulties in the design of digital circuits arise from\nthis fact.\n\nA half-adder circuit.\n\nWe will now build a program for modeling the digital logic circuits we\nwish to study.\n\nThe program will construct computational objects\nmodeling the wires, which will hold the signals.\n\nFunction\nboxes will be modeled by\nfunctions\nthat enforce the correct relationships among the signals.\n\nOne basic element of our simulation will be a\nfunction\nmake_wire,\nwhich constructs wires.\n\nFor example, we can construct six wires as follows:", - "token_count": 288, - "has_code": false, + "content": "It drives its output signal\nto a value that is the\nlogical and of the inputs.\n\nAn\nor-gate is a similar two-input primitive function\nbox that drives its output signal to a value that is the\nlogical or of the inputs.\n\nThat is, the output will become 1 if at least one\nof the input signals is 1; otherwise the output will become 0.\n\nWe can connect primitive functions together to construct more complex\nfunctions.\n\nTo accomplish this we wire the outputs of some\nfunction boxes to the inputs of other function boxes.\n\nFor example,\nthe\nhalf-adder circuit shown in\nfigure consists of an\nor-gate, two and-gates, and an inverter.\n\nIt takes two input signals,\n$A$ and $B$ , and has\ntwo output signals, $S$ and $C$.\n$S$ will become 1\nwhenever precisely one of $A$ and $B$\nis 1, and $C$ will become 1 whenever\n$A$ and $B$ are both 1.\n\nWe can see\nfrom the figure that, because of the\ndelays involved, the outputs may be generated at different times.\n\nMany of the difficulties in the design of digital circuits arise from\nthis fact.\n\nA half-adder circuit.\n\nWe will now build a program for modeling the digital logic circuits we\nwish to study.\n\nThe program will construct computational objects\nmodeling the wires, which will hold the signals.\n\nFunction\nboxes will be modeled by\nfunctions\nthat enforce the correct relationships among the signals.\n\nOne basic element of our simulation will be a\nfunction\nmake_wire,\nwhich constructs wires.\n\nFor example, we can construct six wires as follows:\n\n```javascript\nmake_wire_usage\n make_wire\n\nconst a = make_wire();\nconst b = make_wire();\nconst c = make_wire();\nconst d = make_wire();\nconst e = make_wire();\nconst s = make_wire();\n```", + "token_count": 285, + "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", "subsection": "A Simulator for Digital Circuits", @@ -3320,8 +4580,8 @@ "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_2" }, { - "content": "For example, we can construct six wires as follows:\n\n```javascript\nmake_wire_usage\n make_wire\n\nconst a = make_wire();\nconst b = make_wire();\nconst c = make_wire();\nconst d = make_wire();\nconst e = make_wire();\nconst s = make_wire();\n```\n\nWe attach a function box to a set of wires by calling a\nfunction\nthat constructs that kind of box.\n\nThe arguments to the constructor\nfunction\nare the wires to be attached to the box.\n\nFor example, given\nthat we can construct and-gates, or-gates, and inverters, we can wire\ntogether the half-adder shown in figure :\n\n```javascript\nor_gate_example\n or_gate\n make_wire_usage\n 'ok'\n\nor_gate(a, b, d);\n```\n\n```javascript\nand_gate_example\n and_gate\n make_wire_usage\n 'ok'\n\nand_gate(a, b, c);\n```\n\n```javascript\ninverter_example\n inverter\n make_wire_usage\n 'ok'\n\ninverter(c, e);\n```\n\n```javascript\nand_gate_example_2\n and_gate\n make_wire_usage\n 'ok'\n\nand_gate(d, e, s);\n```\n\nBetter yet, we can explicitly name this operation by defining a function half_@adder that constructs this circuit, given the four external wires to be attached\n\nto the half-adder:\n\n```javascript\nhalf_adder_example\n half_adder\n\nconst a = make_wire();\nconst b = make_wire();\nconst s = make_wire();\nconst c = make_wire();\nhalf_adder(a, b, s, c);\n```\n\n```javascript\nhalf_adder\n make_wire\n or_gate\n and_gate\n inverter\n half_adder_example\n 'ok'\n\nfunction half_adder(a, b, s, c) {\n const d = make_wire();\n const e = make_wire();\n or_gate(a, b, d);\n and_gate(a, b, c);\n inverter(c, e);\n and_gate(d, e, s);\n return \"ok\";\n}\n```\n\nThe advantage of making this definition is that we can use\nhalf_adder\nitself as a building block in creating more complex\ncircuits.\n\nFigure , for example, shows a\nfull-adder composed of two half-adders and an or-gate.\n\n```javascript\nfull_adder_example\n full_adder\n\nconst a = make_wire();\nconst b = make_wire();\nconst c_in = make_wire();\nconst sum = make_wire();\nconst c_out = make_wire();\nfull_adder(a, b, c_in, sum, c_out);\n```", - "token_count": 282, + "content": "For example, we can construct six wires as follows:\n\nThe arguments to the constructor\nfunction\nare the wires to be attached to the box.\n\nFor example, given\nthat we can construct and-gates, or-gates, and inverters, we can wire\ntogether the half-adder shown in figure :\n\n```javascript\nor_gate_example\n or_gate\n make_wire_usage\n 'ok'\n\nor_gate(a, b, d);\n\n\"ok\"\n```\n\n```javascript\nand_gate_example\n and_gate\n make_wire_usage\n 'ok'\n\nand_gate(a, b, c);\n\n\"ok\"\n```\n\n```javascript\ninverter_example\n inverter\n make_wire_usage\n 'ok'\n\ninverter(c, e);\n\n\"ok\"\n```\n\n```javascript\nand_gate_example_2\n and_gate\n make_wire_usage\n 'ok'\n\nand_gate(d, e, s);\n\n\"ok\"\n```\n\nBetter yet, we can explicitly name this operation by defining a function half_@adder that constructs this circuit, given the four external wires to be attached\n\nto the half-adder:\n\n```javascript\nhalf_adder_example\n half_adder\n\nconst a = make_wire();\nconst b = make_wire();\nconst s = make_wire();\nconst c = make_wire();\nhalf_adder(a, b, s, c);\n```\n\n```javascript\nhalf_adder\n make_wire\n or_gate\n and_gate\n inverter\n half_adder_example\n 'ok'\n\nfunction half_adder(a, b, s, c) {\n const d = make_wire();\n const e = make_wire();\n or_gate(a, b, d);\n and_gate(a, b, c);\n inverter(c, e);\n and_gate(d, e, s);\n return \"ok\";\n}\n```\n\nThe advantage of making this definition is that we can use\nhalf_adder\nitself as a building block in creating more complex\ncircuits.\n\nFigure , for example, shows a\nfull-adder composed of two half-adders and an or-gate.\n\nWe can construct a\nfull-adder as follows:\n\n```javascript\nfull_adder_example\n full_adder\n\nconst a = make_wire();\nconst b = make_wire();\nconst c_in = make_wire();\nconst sum = make_wire();\nconst c_out = make_wire();\nfull_adder(a, b, c_in, sum, c_out);\n```\n\n```javascript\nfull_adder\n make_wire\n half_adder\n or_gate\n full_adder_example\n 'ok'\n\nfunction full_adder(a, b, c_in, sum, c_out) {\n const s = make_wire();\n const c1 = make_wire();\n const c2 = make_wire();\n half_adder(b, c_in, s, c1);\n half_adder(a, s, sum, c2);\n or_gate(c1, c2, c_out);\n return \"ok\";\n}\n```\n\nHaving defined\nfull_adder\nas a\nfunction,\nwe can now use it as a building block for creating still more complex\ncircuits.\n\n(For example, see exercise.)", + "token_count": 311, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3330,9 +4590,9 @@ "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_3" }, { - "content": "Figure , for example, shows a\nfull-adder composed of two half-adders and an or-gate.\n\n```javascript\nfull_adder\n make_wire\n half_adder\n or_gate\n full_adder_example\n 'ok'\n\nfunction full_adder(a, b, c_in, sum, c_out) {\n const s = make_wire();\n const c1 = make_wire();\n const c2 = make_wire();\n half_adder(b, c_in, s, c1);\n half_adder(a, s, sum, c2);\n or_gate(c1, c2, c_out);\n return \"ok\";\n}\n```\n\nHaving defined\nfull_adder\nas a\nfunction,\nwe can now use it as a building block for creating still more complex\ncircuits.\n\n(For example, see exercise.)\n\nIn essence, our simulator provides us with the tools to construct a\nlanguage of circuits.\n\nIf we adopt the general perspective on\nlanguages with which we approached the study of\nJavaScript\nin section ,\nwe can say that the primitive function boxes form the primitive\nelements of the language, that wiring boxes together provides a means\nof combination, and that specifying wiring patterns as\nfunctions\nserves as a means of abstraction.\n\nThe primitive function boxes\nforces by which a\nchange in the signal on one wire influences the signals on other\nwires.\n\nTo build function boxes, we use the following operations on\nwires:\n-\n-\nget_signal(wire)\nreturns the current value of the signal on the wire.\n-\n-\nset_signal(wire, new-value):\nchanges the value of the signal on the wire to the new value.\n-\n-\nadd_action(wire, function-of-no-arguments):\nasserts that the designated\nfunction\nshould be run whenever the signal on the wire changes value.\n\nSuch\nfunctions\nare the vehicles by which changes in the signal value on the wire are\ncommunicated to other wires.\n\nIn addition, we will make use of a\nfunction\nafter_delay\nthat takes a time delay and a\nfunction\nto be run and executes the given\nfunction\nafter the given delay.\n\nUsing these\nfunctions,\nwe can define the primitive digital logic functions.", - "token_count": 294, - "has_code": true, + "content": "(For example, see exercise.)\n\nIf we adopt the general perspective on\nlanguages with which we approached the study of\nJavaScript\nin section ,\nwe can say that the primitive function boxes form the primitive\nelements of the language, that wiring boxes together provides a means\nof combination, and that specifying wiring patterns as\nfunctions\nserves as a means of abstraction.\n\nThe primitive function boxes\nimplement the forces by which a\nchange in the signal on one wire influences the signals on other\nwires.\n\nTo build function boxes, we use the following operations on\nwires:\n-\n-\nget_signal(wire)\nreturns the current value of the signal on the wire.\n-\n-\nset_signal(wire, new-value):\nchanges the value of the signal on the wire to the new value.\n-\n-\nadd_action(wire, function-of-no-arguments):\nasserts that the designated\nfunction\nshould be run whenever the signal on the wire changes value.\n\nSuch\nfunctions\nare the vehicles by which changes in the signal value on the wire are\ncommunicated to other wires.\n\nIn addition, we will make use of a\nfunction\nafter_delay\nthat takes a time delay and a\nfunction\nto be run and executes the given\nfunction\nafter the given delay.\n\nUsing these\nfunctions,\nwe can define the primitive digital logic functions.\n\nTo connect an input\nto an output through an inverter, we use\nadd_action\nto associate with the input wire a\nfunction\nthat will be run whenever the signal on the input wire changes value.\n\nThe\nfunction\ncomputes the\nlogical_not\nof the input signal, and then, after one\ninverter_delay,\nsets the output signal to be this new value:", + "token_count": 262, + "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", "subsection": "A Simulator for Digital Circuits", @@ -3340,8 +4600,8 @@ "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_4" }, { - "content": "Using these\nfunctions,\nwe can define the primitive digital logic functions.\n\nTo connect an input\nto an output through an inverter, we use\nadd_action\nto associate with the input wire a\nfunction\nthat will be run whenever the signal on the input wire changes value.\n\nThe\nfunction\ncomputes the\nlogical_not\nof the input signal, and then, after one\ninverter_delay,\nsets the output signal to be this new value:\n\n```javascript\ninverter\n get_signal\n after_delay\n inverter_example\n 'ok'\n\nfunction inverter(input, output) {\n function invert_input() {\n const new_value = logical_not(get_signal(input));\n after_delay(inverter_delay,\n () => set_signal(output, new_value));\n }\n add_action(input, invert_input);\n return \"ok\";\n}\nfunction logical_not(s) {\n return s === 0\n ? 1\n : s === 1\n ? 0\n : error(s, \"invalid signal\");\n}\n```\n\nAn and-gate is a little more complex.\n\nThe action\nfunction\nmust be run if\neither of the inputs to the gate changes.\n\nIt computes the\n\n```javascript\nlogical_and\n\t(using a function analogous to\n\tlogical_not)\n```\n\nof the values of the signals on the input wires and sets up a change to the new value to occur on the output wire\n\nafter one and_gate_delay.\n\n```javascript\nand_gate\n get_signal\n after_delay\n logical_and\n and_gate_example\n 'ok'\n\nfunction and_gate(a1, a2, output) {\n function and_action_function() {\n const new_value = logical_and(get_signal(a1),\n get_signal(a2));\n after_delay(and_gate_delay,\n () => set_signal(output, new_value));\n }\n add_action(a1, and_action_function);\n add_action(a2, and_action_function);\n return \"ok\";\n}\n```\n\nA wire\nasignal_value\n(initially taken to be 0) and a collection of\naction_functions\nto be run when the signal changes value.\n\nWe implement the wire,\nusing\nfunctions\ntogether with a function\nthat selects the appropriate local operation, just as we did\nwith the simple bank-account object in section\n:", - "token_count": 262, + "content": "The\nfunction\ncomputes the\nlogical_not\nof the input signal, and then, after one\ninverter_delay,\nsets the output signal to be this new value:\n\nAn and-gate is a little more complex.\n\nThe action\nfunction\nmust be run if\neither of the inputs to the gate changes.\n\nIt computes the\nlogical_and (using a function analogous to logical_not)\nof the values of the signals on the input wires and sets up a change\nto the new value to occur on the output wire after one\nand_gate_delay.\n\n```javascript\nand_gate\n get_signal\n after_delay\n logical_and\n and_gate_example\n 'ok'\n\nfunction and_gate(a1, a2, output) {\n function and_action_function() {\n const new_value = logical_and(get_signal(a1),\n get_signal(a2));\n after_delay(and_gate_delay,\n () => set_signal(output, new_value));\n }\n add_action(a1, and_action_function);\n add_action(a2, and_action_function);\n return \"ok\";\n}\n```\n\nA wire\nin our simulation will be a computational object with two local\nstate variables:\nasignal_value\n(initially taken to be 0) and a collection of\naction_functions\nto be run when the signal changes value.\n\nWe implement the wire,\nusing\nmessage-passing style, as\na collection of local\nfunctions\ntogether with a\nfunction\nthat selects the appropriate local operation, just as we did\nwith the simple bank-account object in section\n:\n\n```javascript\nmake_wire\n call_each\n make_wire_usage\n\nfunction make_wire() {\n let signal_value = 0;\n let action_functions = null;\n function set_my_signal(new_value) {\n if (signal_value !== new_value) {\n signal_value = new_value;\n return call_each(action_functions);\n } else {\n return \"done\";\n }\n }\n function accept_action_function(fun) {\n action_functions = pair(fun, action_functions);\n fun();\n }\n function dispatch(m) {\n return m === \"get_signal\"\n ? signal_value\n : m === \"set_signal\"\n ? set_my_signal\n : m === \"add_action\"\n ? accept_action_function\n : error(m, \"unknown operation -- wire\");\n }\n return dispatch;\n}\n```\n\nThe local\nfunction\nset_my_signal\ntests whether the new signal value changes the signal on the wire.\n\nIf so, it runs each of the action\nfunctions,\nusing the following\nfunction\ncall_each,\nwhich calls each of the items in a list of no-argument\nfunctions:", + "token_count": 304, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3350,8 +4610,8 @@ "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_5" }, { - "content": "We implement the wire,\nusing\nfunctions\ntogether with a function\nthat selects the appropriate local operation, just as we did\nwith the simple bank-account object in section\n:\n\n```javascript\nmake_wire\n call_each\n make_wire_usage\n\nfunction make_wire() {\n let signal_value = 0;\n let action_functions = null;\n function set_my_signal(new_value) {\n if (signal_value !== new_value) {\n signal_value = new_value;\n return call_each(action_functions);\n } else {\n return \"done\";\n }\n }\n function accept_action_function(fun) {\n action_functions = pair(fun, action_functions);\n fun();\n }\n function dispatch(m) {\n return m === \"get_signal\"\n ? signal_value\n : m === \"set_signal\"\n ? set_my_signal\n : m === \"add_action\"\n ? accept_action_function\n : error(m, \"unknown operation -- wire\");\n }\n return dispatch;\n}\n```\n\nThe local\nfunction\nset_my_signal\ntests whether the new signal value changes the signal on the wire.\n\nIf so, it runs each of the action\nfunctions,\nusing the following\nfunction\ncall_each,\nwhich calls each of the items in a list of no-argument\nfunctions:\n\n```javascript\ncall_each\n\nfunction call_each(functions) {\n if (is_null(functions)) {\n return \"done\";\n } else {\n head(functions)();\n return call_each(tail(functions));\n }\n}\n```\n\nThe local\nfunction\naccept_action_function\nadds the given\nfunction\nto the list of\nfunctions\nto be run, and then runs the new\nfunction\nonce.\n\n(See exercise.)\n\nWith the local function set up as specified, we can provide the following functions to access the local operations on wires:\n\n```javascript\nget_signal\n\nfunction get_signal(wire) {\n return wire(\"get_signal\");\n}\nfunction set_signal(wire, new_value) {\n return wire(\"set_signal\")(new_value);\n}\nfunction add_action(wire, action_function) {\n return wire(\"add_action\")(action_function);\n}\n```\n\nWires, which have time-varying signals and may be incrementally attached to\ndevices, are typical of mutable objects.\n\nWe have modeled them as\nfunctions\nwith local state variables that are modified by assignment.\n\nWhen a new\nwire is created, a new set of state variables is allocated (by the\nlet statements in\nmake_wire)\nand a new\nfunction\nis constructed and returned, capturing\nthe environment with the new state variables.", - "token_count": 302, + "content": "If so, it runs each of the action\nfunctions,\nusing the following\nfunction\ncall_each,\nwhich calls each of the items in a list of no-argument\nfunctions:\n\nThe local\nfunction\naccept_action_function\nadds the given\nfunction\nto the list of\nfunctions\nto be run, and then runs the new\nfunction\nonce.\n\n(See exercise.)\n\nWith the local function set up as specified, we can provide the following functions to access the local operations on wires:\n\n```javascript\nget_signal\n\nfunction get_signal(wire) {\n return wire(\"get_signal\");\n}\nfunction set_signal(wire, new_value) {\n return wire(\"set_signal\")(new_value);\n}\nfunction add_action(wire, action_function) {\n return wire(\"add_action\")(action_function);\n}\n```\n\nWires, which have time-varying signals and may be incrementally attached to\ndevices, are typical of mutable objects.\n\nWe have modeled them as\nfunctions\nwith local state variables that are modified by assignment.\n\nWhen a new\nwire is created, a new set of state variables is allocated (by the\nlet statements in\nmake_wire)\nand a new\nfunction\nis constructed and returned, capturing\nthe environment with the new state variables.\n\nThe wires are shared among the various devices that have been\nconnected to them.\n\nThus, a change made by an interaction with one\ndevice will affect all the other devices attached to the wire.\n\nThe\nwire communicates the change to its neighbors by calling the action\nfunctions\nprovided to it when the connections were established.\n\nThe only thing needed to complete the simulator is\nafter_delay.\n\nThe idea here is that we maintain a data structure, called an\nagenda , that contains a schedule of things to do.", + "token_count": 250, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3360,8 +4620,8 @@ "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_6" }, { - "content": "When a new\nwire is created, a new set of state variables is allocated (by the\nlet statements in\nmake_wire)\nand a new\nfunction\nis constructed and returned, capturing\nthe environment with the new state variables.\n\nThe wires are shared among the various devices that have been\nconnected to them.\n\nThus, a change made by an interaction with one\ndevice will affect all the other devices attached to the wire.\n\nThe\nwire communicates the change to its neighbors by calling the action\nfunctions\nprovided to it when the connections were established.\n\nThe only thing needed to complete the simulator is\nafter_delay.\n\nThe idea here is that we maintain a data structure, called an\nagenda , that contains a schedule of things to do.\n\nThe following operations are defined for agendas:\n-\n-\nmake_agenda():\nreturns a new empty agenda.\n-\n-\nis_empty_agenda(agenda)\nis true if the specified agenda is empty.\n-\n-\nfirst_agenda_item(agenda)\nreturns the first item on the agenda.\n-\n-\nremove_first_agenda_item(agenda)\nmodifies the agenda by removing the first item.\n-\n-\nadd_to_agenda(time, action, agenda)\nmodifies the agenda by adding the given action\nfunction\nto be run at the specified time.\n-\n-\ncurrent_time(agenda)\nreturns the current simulation time.\n\nThe particular agenda that we use is denoted by\nthe_agenda.\n\nThe\nfunction\nafter_delay\nadds new elements to\nthe_agenda:\n\n```javascript\nafter_delay\n add_to_agenda\n make_agenda\n the_agenda\n\nfunction after_delay(delay, action) {\n add_to_agenda(delay + current_time(the_agenda),\n action,\n the_agenda);\n}\n```\n\n```javascript\nThe simulation is driven by the function\n\tthe_agenda\n\tin sequence.\n```\n\nIn general, as the simulation runs, new items will be added to the agenda, and\n\n```javascript\npropagate\n remove_first_agenda_item\n first_agenda_item\n the_agenda\n\nfunction propagate() {\n if (is_empty_agenda(the_agenda)) {\n return \"done\";\n } else {\n const first_item = first_agenda_item(the_agenda);\n first_item();\n remove_first_agenda_item(the_agenda);\n return propagate();\n }\n}\n```\n\nThe following\nfunction,\nwhich places a probe on a wire, shows the simulator in\naction.", - "token_count": 302, + "content": "The idea here is that we maintain a data structure, called an\nagenda , that contains a schedule of things to do.\n\nThe particular agenda that we use is denoted by\nthe_agenda.\n\nThe\nfunction\nafter_delay\nadds new elements to\nthe_agenda:\n\n```javascript\nafter_delay\n add_to_agenda\n make_agenda\n the_agenda\n\nfunction after_delay(delay, action) {\n add_to_agenda(delay + current_time(the_agenda),\n action,\n the_agenda);\n}\n```\n\nThe simulation is driven by the function , which executes each function on the_agenda in sequence.\n\nIn general, as the simulation runs, new items\nwill be added to the agenda, and\nwill continue the simulation as long as there are items on the agenda:\n\n```javascript\npropagate\n remove_first_agenda_item\n first_agenda_item\n the_agenda\n\nfunction propagate() {\n if (is_empty_agenda(the_agenda)) {\n return \"done\";\n } else {\n const first_item = first_agenda_item(the_agenda);\n first_item();\n remove_first_agenda_item(the_agenda);\n return propagate();\n }\n}\n```\n\nThe following\nfunction,\nwhich places a probe on a wire, shows the simulator in\naction.\n\nThe probe tells the wire that, whenever its signal changes value,\nit should print the new signal value, together with the current time and\na name that identifies the\nwire.\n\n```javascript\nprobe\n the_agenda\n get_signal\n\nfunction probe(name, wire) {\n add_action(wire,\n () => display(name + \" \" +\n stringify(current_time(the_agenda)) +\n \", new value = \" +\n stringify(get_signal(wire))));\n}\n\nfunction probe(name, wire) {\n add_action(wire,\n () => name + \" \" +\n stringify(current_time(the_agenda)) +\n \", new value = \" +\n stringify(get_signal(wire)));\n}\n```\n\nWe begin by initializing the agenda and specifying delays for the primitive function boxes:\n\n```javascript\nthe_agenda\n make_agenda\n\nconst the_agenda = make_agenda();\nconst inverter_delay = 2;\nconst and_gate_delay = 3;\nconst or_gate_delay = 5;\n```\n\nNow we define four wires, placing probes on two of them:\n\n```javascript\nprobing_two_wires\n make_wire\n probe\n\nconst input_1 = make_wire();\nconst input_2 = make_wire();\nconst sum = make_wire();\nconst carry = make_wire();\n\nprobe(\"sum\", sum);\n\n\"sum 0, new value = 0\"\n```\n\n```javascript\nprobe_carry\n probing_two_wires\n\nprobe(\"carry\", carry);\n\n\"carry 0, new value = 0\"\n```", + "token_count": 307, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3370,8 +4630,8 @@ "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_7" }, { - "content": "The following\nfunction,\nwhich places a probe on a wire, shows the simulator in\naction.\n\nThe probe tells the wire that, whenever its signal changes value,\nit should print the new signal value, together with the current time and\na name that identifies the\nwire.\n\n```javascript\nprobe\n the_agenda\n get_signal\n\nfunction probe(name, wire) {\n add_action(wire,\n () => display(name + \" \" +\n stringify(current_time(the_agenda)) +\n \", new value = \" +\n stringify(get_signal(wire))));\n}\n\nfunction probe(name, wire) {\n add_action(wire,\n () => name + \" \" +\n stringify(current_time(the_agenda)) +\n \", new value = \" +\n stringify(get_signal(wire)));\n}\n```\n\nWe begin by initializing the agenda and specifying delays for the primitive function boxes:\n\n```javascript\nthe_agenda\n make_agenda\n\nconst the_agenda = make_agenda();\nconst inverter_delay = 2;\nconst and_gate_delay = 3;\nconst or_gate_delay = 5;\n```\n\nNow we define four wires, placing probes on two of them:\n\n```javascript\nprobing_two_wires\n make_wire\n probe\n\nconst input_1 = make_wire();\nconst input_2 = make_wire();\nconst sum = make_wire();\nconst carry = make_wire();\n\nprobe(\"sum\", sum);\n```\n\n```javascript\nprobe_carry\n probing_two_wires\n\nprobe(\"carry\", carry);\n```\n\nNext we connect the wires in a half-adder circuit (as in figure ), set the signal on input_1 to 1, and run the simulation:\n\n```javascript\nhalf_adder_example_2\n half_adder\n probe_carry\n 'ok'\n\nhalf_adder(input_1, input_2, sum, carry);\n```\n\n```javascript\nset_signal_example\n half_adder_example_2\n 'done'\n\nset_signal(input_1, 1);\n```\n\n```javascript\npropagate_example_1\n set_signal_example\n propagate\n 'done'\n\npropagate();\n```\n\nThe input_2 to 1 and allow the values to propagate:\n\n```javascript\nset_signal_example_2\n propagate_example_1\n 'done'\n\nset_signal(input_2, 1);\n```\n\n```javascript\npropagate_example_2\n set_signal_example_2\n 'done'\n\npropagate();\n```\n\nThe\n\nFinally, we give details of the agenda data structure, which holds the functions that are scheduled for future execution.\n\nThe agenda is made up of\ntime segments.\n\nEach time segment is a\npair consisting of a number (the time) and a\n) that holds the\nfunctions\nthat are scheduled to be run during that time segment.\n\n```javascript\nmake_time_segment\n\nfunction make_time_segment(time, queue) {\n return pair(time, queue);\n}\nfunction segment_time(s) { return head(s); }\n\nfunction segment_queue(s) { return tail(s); }\n```", - "token_count": 319, + "content": "Now we define four wires, placing probes on two of them:\n\n```javascript\nhalf_adder_example_2\n half_adder\n probe_carry\n 'ok'\n\nhalf_adder(input_1, input_2, sum, carry);\n\n\"ok\"\n```\n\n```javascript\nset_signal_example\n half_adder_example_2\n 'done'\n\nset_signal(input_1, 1);\n\n\"done\"\n```\n\n```javascript\npropagate_example_1\n set_signal_example\n propagate\n 'done'\n\npropagate();\n\n\"sum 8, new value = 1\"\n\"done\"\n```\n\nThe signal changes to 1 at time 8.\n\nWe are now eight time units from the beginning of the simulation.\n\nAt this point, we can set the signal on\ninput_2\nto 1 and allow the values to propagate:\n\n```javascript\nset_signal_example_2\n propagate_example_1\n 'done'\n\nset_signal(input_2, 1);\n\n\"done\"\n```\n\n```javascript\npropagate_example_2\n set_signal_example_2\n 'done'\n\npropagate();\n\n\"carry 11, new value = 1\"\n\"sum 16, new value = 0\"\n\"done\"\n```\n\nThe changes to 1 at time 11 and the changes to 0 at time 16.\n\nFinally, we give details of the agenda data structure, which holds the functions that are scheduled for future execution.\n\nThe agenda is made up of\ntime segments.\n\nEach time segment is a\npair consisting of a number (the time) and a\nqueue (see\nexercise ) that holds the\nfunctions\nthat are scheduled to be run during that time segment.\n\n```javascript\nmake_time_segment\n\nfunction make_time_segment(time, queue) {\n return pair(time, queue);\n}\nfunction segment_time(s) { return head(s); }\n\nfunction segment_queue(s) { return tail(s); }\n```\n\nWe will operate on the time-segment queues using the queue operations described in section.\n\nThe agenda itself is a one-dimensional\ntable of time segments.\n\nIt\ndiffers from the tables described in section\nin that the segments will be sorted in order of increasing time.\n\nIn\naddition, we store the\ncurrent time (i.e., the time of the last action\nthat was processed) at the head of the agenda.\n\nA newly constructed\nagenda has no time segments and has a current time of 0:", + "token_count": 288, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3380,8 +4640,8 @@ "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_8" }, { - "content": "Each time segment is a\npair consisting of a number (the time) and a\n) that holds the\nfunctions\nthat are scheduled to be run during that time segment.\n\nWe will operate on the time-segment queues using the queue operations described in section.\n\nThe agenda itself is a one-dimensional\nin that the segments will be sorted in order of increasing time.\n\nIn\naddition, we store the\ncurrent time (i.e., the time of the last action\nthat was processed) at the head of the agenda.\n\nA newly constructed\nagenda has no time segments and has a current time of 0:\n\n```javascript\nmake_agenda\n\nfunction make_agenda() { return list(0); }\n\nfunction current_time(agenda) { return head(agenda); }\n\nfunction set_current_time(agenda, time) {\n set_head(agenda, time);\n}\nfunction segments(agenda) { return tail(agenda); }\n\nfunction set_segments(agenda, segs) {\n set_tail(agenda, segs);\n}\nfunction first_segment(agenda) { return head(segments(agenda)); }\n\nfunction rest_segments(agenda) { return tail(segments(agenda)); }\n```\n\nAn agenda is empty if it has no time segments:\n\n```javascript\nis_empty_agenda\n make_agenda\n\nfunction is_empty_agenda(agenda) {\n return is_null(segments(agenda));\n}\n```\n\nTo add an action to an agenda, we first check if the agenda is empty.\n\nIf so, we create a time segment for the action and install this in\nthe agenda.\n\nOtherwise, we scan the agenda, examining the time of each\nsegment.\n\nIf we find a segment for our appointed time, we add the\naction to the associated queue.\n\nIf we reach a time later than the one\nto which we are appointed, we insert a new time segment into the\nagenda just before it.\n\nIf we reach the end of the agenda, we must\ncreate a new time segment at the end.", - "token_count": 269, + "content": "A newly constructed\nagenda has no time segments and has a current time of 0:\n\nAn agenda is empty if it has no time segments:\n\n```javascript\nis_empty_agenda\n make_agenda\n\nfunction is_empty_agenda(agenda) {\n return is_null(segments(agenda));\n}\n```\n\nTo add an action to an agenda, we first check if the agenda is empty.\n\nIf so, we create a time segment for the action and install this in\nthe agenda.\n\nOtherwise, we scan the agenda, examining the time of each\nsegment.\n\nIf we find a segment for our appointed time, we add the\naction to the associated queue.\n\nIf we reach a time later than the one\nto which we are appointed, we insert a new time segment into the\nagenda just before it.\n\nIf we reach the end of the agenda, we must\ncreate a new time segment at the end.\n\n```javascript\nadd_to_agenda\n make_time_segment\n make_queue\n insert_queue\n make_time_segment\n make_agenda\n\nfunction add_to_agenda(time, action, agenda) {\n function belongs_before(segs) {\n return is_null(segs) || time < segment_time(head(segs));\n }\n function make_new_time_segment(time, action) {\n const q = make_queue();\n insert_queue(q, action);\n return make_time_segment(time, q);\n }\n function add_to_segments(segs) {\n if (segment_time(head(segs)) === time) {\n insert_queue(segment_queue(head(segs)), action);\n } else {\n const rest = tail(segs);\n if (belongs_before(rest)) {\n set_tail(segs, pair(make_new_time_segment(time, action),\n tail(segs)));\n } else {\n add_to_segments(rest);\n }\n }\n }\n const segs = segments(agenda);\n if (belongs_before(segs)) {\n set_segments(agenda,\n pair(make_new_time_segment(time, action), segs));\n } else {\n add_to_segments(segs);\n }\n}\n```\n\nThe\nfunction\nthat removes the first item from the agenda deletes the\nitem at the front of the queue in the first time segment.\n\nIf this\ndeletion makes the time segment empty, we remove it from the list of\nsegments:\n\n```javascript\nremove_first_agenda_item\n make_agenda\n is_empty_queue\n delete_queue\n make_time_segment\n\nfunction remove_first_agenda_item(agenda) {\n const q = segment_queue(first_segment(agenda));\n delete_queue(q);\n if (is_empty_queue(q)) {\n set_segments(agenda, rest_segments(agenda));\n } else {}\n}\n```\n\nThe first agenda item is found at the head of the queue in the first\ntime segment.", + "token_count": 304, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3390,8 +4650,8 @@ "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_9" }, { - "content": "If we reach the end of the agenda, we must\ncreate a new time segment at the end.\n\n```javascript\nadd_to_agenda\n make_time_segment\n make_queue\n insert_queue\n make_time_segment\n make_agenda\n\nfunction add_to_agenda(time, action, agenda) {\n function belongs_before(segs) {\n return is_null(segs) || time < segment_time(head(segs));\n }\n function make_new_time_segment(time, action) {\n const q = make_queue();\n insert_queue(q, action);\n return make_time_segment(time, q);\n }\n function add_to_segments(segs) {\n if (segment_time(head(segs)) === time) {\n insert_queue(segment_queue(head(segs)), action);\n } else {\n const rest = tail(segs);\n if (belongs_before(rest)) {\n set_tail(segs, pair(make_new_time_segment(time, action),\n tail(segs)));\n } else {\n add_to_segments(rest);\n }\n }\n }\n const segs = segments(agenda);\n if (belongs_before(segs)) {\n set_segments(agenda,\n pair(make_new_time_segment(time, action), segs));\n } else {\n add_to_segments(segs);\n }\n}\n```\n\nThe\nfunction\nthat removes the first item from the agenda deletes the\nitem at the front of the queue in the first time segment.\n\nIf this\ndeletion makes the time segment empty, we remove it from the list of\nsegments:\n\n```javascript\nremove_first_agenda_item\n make_agenda\n is_empty_queue\n delete_queue\n make_time_segment\n\nfunction remove_first_agenda_item(agenda) {\n const q = segment_queue(first_segment(agenda));\n delete_queue(q);\n if (is_empty_queue(q)) {\n set_segments(agenda, rest_segments(agenda));\n } else {}\n}\n```\n\nThe first agenda item is found at the head of the queue in the first\ntime segment.\n\nWhenever we extract an item, we also update the current\ntime:\n\n```javascript\nfirst_agenda_item\n is_empty_agenda\n make_time_segment\n front_queue\n\nfunction first_agenda_item(agenda) {\n if (is_empty_agenda(agenda)) {\n error(\"agenda is empty -- first_agenda_item\");\n } else {\n const first_seg = first_segment(agenda);\n set_current_time(agenda, segment_time(first_seg));\n return front_queue(segment_queue(first_seg));\n }\n}\n```", - "token_count": 225, + "content": "The first agenda item is found at the head of the queue in the first\ntime segment.\n\n```javascript\nfirst_agenda_item\n is_empty_agenda\n make_time_segment\n front_queue\n\nfunction first_agenda_item(agenda) {\n if (is_empty_agenda(agenda)) {\n error(\"agenda is empty -- first_agenda_item\");\n } else {\n const first_seg = first_segment(agenda);\n set_current_time(agenda, segment_time(first_seg));\n return front_queue(segment_queue(first_seg));\n }\n}\n```", + "token_count": 47, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3400,8 +4660,8 @@ "chunk_id": "Modularity_Objects_and_State_A_Simulator_for_Digital_Circuits_10" }, { - "content": "The basic operations on\npairspair,\nhead,\nand\ntailcan\nbe used to construct list structure and to select parts\nfrom list structure, but they are incapable of modifying list\nstructure.\n\nThe same is true of the list operations we have used so\nfar, such as\npair,\nhead,\nand\ntail.\n\nTo modify list structures we need new operations.\n\nThe primitive mutators for pairs are\nset_head\nand\nset_tail.\n\nThe function set_head\ntakes two arguments, the first of which must be a pair.\n\nIt modifies this\npair, replacing the\nhead\npointer by a pointer to the second argument of\nset_head.\n\nAs an example, suppose that\nlist(list(\"a\", \"b\"), \"c\", \"d\")\nand\nlist(\"e\", \"f\")\nas illustrated in\nfigure.\n\nEvaluating the expression\nset_head(x, y)\nmodifies the pair to which\nhead\nby the value of\nfigure.\n\nThe structure\n\n```javascript\nis now equivalent to\n\tlist(list(\"e\", \"f\"), \"c\", \"d\").\n```\n\nThe pairs representing the list list(\"a\", \"b\"), identified by the pointer that was replaced, are now detached from the original structure.\n\nCompare figure with figure, which illustrates the result of executing\n\n```javascript\nmutable_list_example\n\nconst x = list(list(\"a\", \"b\"), \"c\");\nconst y = list(\"e\", \"f\");\n```\n\n```javascript\nmutable_list_example\n\nconst z = pair(y, tail(x));\n```\n\nwith\nfigure.\n\nThe\nname\npair\noperation; the list to which\n\nThe\nset_tail\noperation is similar to\nset_head.\n\nThe only difference is that the\ntail\npointer of the pair, rather than the\nhead\npointer, is replaced.\n\nThe effect of executing\nset_tail(x, y)\non the lists of\nfigure\nis shown in\nfigure.\n\nHere the\ntail\npointer of\nlist(\"e\", \"f\").\n\nAlso, the list\nlist(\"c\", \"d\"),\nwhich used to be the\ntail\nof\n\nThe function pair\nbuilds new list structure by creating new pairs,\nwhereas set_@head\nand\nset_tail\nmodify existing pairs.", - "token_count": 280, + "content": "The basic operations on\npairspair,\nhead,\nand\ntailcan\nbe used to construct list structure and to select parts\nfrom list structure, but they are incapable of modifying list\nstructure.\n\nThe same is true of the list operations we have used so\nfar, such as and\n, since these can be defined in terms of\npair,\nhead,\nand\ntail.\n\nTo modify list structures we need new operations.\n\nThe primitive mutators for pairs are\nset_head\nand\nset_tail.\n\nThe function set_head\ntakes two arguments, the first of which must be a pair.\n\nIt modifies this\npair, replacing the\nhead\npointer by a pointer to the second argument of\nset_head.\n\nAs an example, suppose that is bound to\nlist(list(\"a\", \"b\"), \"c\", \"d\")\nand to\nlist(\"e\", \"f\")\nas illustrated in\nfigure.\n\nEvaluating the expression\nset_head(x, y)\nmodifies the pair to which is bound,\nreplacing its\nhead\nby the value of.\n\nThe result of the operation\nis shown in\nfigure.\n\nThe structure has been modified and\nis now equivalent to list(list(\"e\", \"f\"), \"c\", \"d\").\n\nThe pairs representing the list\nlist(\"a\", \"b\"),\nidentified by the pointer that was replaced, are now detached from the\noriginal structure.\n\nCompare figure with figure, which illustrates the result of executing\n\n```javascript\nmutable_list_example\n\nconst x = list(list(\"a\", \"b\"), \"c\");\nconst y = list(\"e\", \"f\");\n```\n\n```javascript\nmutable_list_example\n\nconst z = pair(y, tail(x));\n```\n\nwith and\nbound to the original lists of\nfigure.\n\nThe\nname\nis now bound to a\nnew pair created by the\npair\noperation; the list to which is bound is\nunchanged.\n\nThe\nset_tail\noperation is similar to\nset_head.\n\nThe only difference is that the\ntail\npointer of the pair, rather than the\nhead\npointer, is replaced.\n\nThe effect of executing\nset_tail(x, y)\non the lists of\nfigure\nis shown in\nfigure.", + "token_count": 292, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3410,8 +4670,8 @@ "chunk_id": "Modularity_Objects_and_State_Mutable_List_Structure_1" }, { - "content": "The function pair\nbuilds new list structure by creating new pairs,\nwhereas set_@head\nand\nset_tail\nmodify existing pairs.\n\nIndeed, we could\npair\nin terms of the two mutators, together with a\nfunction\nget_new_pair,\nwhich returns a new pair that is not part of any existing list structure.\n\nWe obtain the new pair, set its\nhead\nand\ntail\npointers to the designated objects, and return the new pair as the result of\nthe\npair.\n\n```javascript\nget_new_pair\n\n// The book proposes a primitive function get_new_pair.\n// Since JavaScript does not provide such a function, let's\n// define it as follows, for the sake of the example.\n\nfunction get_new_pair() {\n return pair(undefined, undefined);\n}\n{\n```\n\n```javascript\nget_new_pair\n mutable_pair_example\n [ [ 1, 2 ], 4 ]\n\nfunction pair(x, y) {\n const fresh = get_new_pair();\n set_head(fresh, x);\n set_tail(fresh, y);\n return fresh;\n}\n```\n\n```javascript\nmutable_pair_example\n\npair(pair(1, 2), 4);\n}\n```\n\nWe mentioned in section the\ntheoretical issues of\nsameness and change\nraised by the introduction of assignment.\n\nThese issues arise in practice\nwhen individual pairs are shared among different data objects.\n\nFor example, consider the structure formed by\n\n```javascript\npair_example1\n\nconst x = list(\"a\", \"b\");\nconst z1 = pair(x, x);\n```\n\nAs shown in\nfigure,\nhead\nand\ntail\nboth point to the same pair\nhead\nand\ntail\nof\npair\nis implemented.\n\nIn general, using\npair\nto construct lists will result in an interlinked structure of pairs in\nwhich many individual pairs are shared by many different structures.\n\nThe list pair(x, x).\n\nThe list pair(list(\"a\", \"b\"), list(\"a\", \"b\")).\n\nIn contrast to\n\n```javascript\nfigure,\n\tfigure\n```\n\nshows the structure created by\n\n```javascript\npair_example2\n\nconst z2 = pair(list(\"a\", \"b\"), list(\"a\", \"b\"));\n```\n\nIn this structure, the pairs in the two list(\"a\", \"b\") lists are distinct, although they contain the same strings.\n\nWhen thought of as a list, the same list:", - "token_count": 303, + "content": "The effect of executing\nset_tail(x, y)\non the lists of\nfigure\nis shown in\nfigure.\n\nAlso, the list\nlist(\"c\", \"d\"),\nwhich used to be the\ntail\nof , is now detached from the structure.\n\nThe function pair\nbuilds new list structure by creating new pairs,\nwhereas set_@head\nand\nset_tail\nmodify existing pairs.\n\nIndeed, we could\nimplement\npair\nin terms of the two mutators, together with a\nfunction\nget_new_pair,\nwhich returns a new pair that is not part of any existing list structure.\n\nWe obtain the new pair, set its\nhead\nand\ntail\npointers to the designated objects, and return the new pair as the result of\nthe\npair.\n\n```javascript\nget_new_pair\n\n// The book proposes a primitive function get_new_pair.\n// Since JavaScript does not provide such a function, let's\n// define it as follows, for the sake of the example.\n\nfunction get_new_pair() {\n return pair(undefined, undefined);\n}\n{\n```\n\n```javascript\nget_new_pair\n mutable_pair_example\n [ [ 1, 2 ], 4 ]\n\nfunction pair(x, y) {\n const fresh = get_new_pair();\n set_head(fresh, x);\n set_tail(fresh, y);\n return fresh;\n}\n```\n\n```javascript\nmutable_pair_example\n\npair(pair(1, 2), 4);\n}\n```\n\nWe mentioned in section the\ntheoretical issues of\nsameness and change\nraised by the introduction of assignment.\n\nThese issues arise in practice\nwhen individual pairs are shared among different data objects.\n\nFor example, consider the structure formed by\n\n```javascript\npair_example1\n\nconst x = list(\"a\", \"b\");\nconst z1 = pair(x, x);\n```\n\nAs shown in\nfigure,\nis a pair whose\nhead\nand\ntail\nboth point to the same pair.\n\nThis sharing\nof by the\nhead\nand\ntail\nof is a consequence of the straightforward\nway in which\npair\nis implemented.\n\nIn general, using\npair\nto construct lists will result in an interlinked structure of pairs in\nwhich many individual pairs are shared by many different structures.\n\nThe list formed by pair(x, x).", + "token_count": 302, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3420,8 +4680,8 @@ "chunk_id": "Modularity_Objects_and_State_Mutable_List_Structure_2" }, { - "content": "When thought of as a list, the same list:\n\n```javascript\nabab\n [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ]\n\nlist(list(\"a\", \"b\"), \"a\", \"b\")\n\nlist(list(\"a\", \"b\"), \"a\", \"b\");\n```\n\nIn general, sharing is completely undetectable if we operate on lists using\nonly\npair,\nhead,\nand\ntail.\n\nHowever, if we allow mutators on list structure, sharing becomes\nsignificant.\n\nAs an example of the difference that sharing can make,\nconsider the following\nfunction,\nwhich modifies the\nhead\nof the structure to which it is applied:\n\n```javascript\nset_to_wow\n\nfunction set_to_wow(x) {\n set_head(head(x), \"wow\");\n return x;\n}\n```\n\nEven though the same structure,\napplying\nset_to_wow\nto them yields different results.\n\nWith\nhead\nalso changes the\ntail,\nbecause in\nhead\nand the\ntail\nare the same pair.\n\nWith\nhead\nand\ntail\nare distinct, so\nset_to_wow\nmodifies only the\nhead:\n\n```javascript\nset_to_wow_example_1\n pair_example1\n [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ]\n\nz1;\n```\n\n```javascript\nset_to_wow_example_2\n set_to_wow\n pair_example1\n [ [ 'wow', [ 'b', null ] ], [ 'wow', [ 'b', null ] ] ]\n\nset_to_wow(z1);\n```\n\n```javascript\nset_to_wow_example_3\n pair_example2\n [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ]\n\nz2;\n```\n\n```javascript\nset_to_wow_example_4\n set_to_wow\n pair_example2\n [ [ 'wow', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ]\n\nset_to_wow(z2);\n```\n\n```javascript\nOne way to detect sharing in list structures is to use the\n\t===,\n\twhich we introduced in\n\tsection to test whether two numbers\n\tare equal\n\tand extended in section to test whether\n\ttwo strings are equal. When applied to two nonprimitive values,\n\tx === y\n\ttests whether\n```\n\nThus, with\n\n```javascript\nfigure\n\tand,\n```\n\n```javascript\nhead(z1) === tail(z1)\n```\n\nis true and\n\n```javascript\nhead(z2) === tail(z2)\n```\n\nis false.", - "token_count": 297, + "content": "The list formed by pair(x, x).\n\nIn contrast to figure, figure shows the structure created by\n\n```javascript\npair_example2\n\nconst z2 = pair(list(\"a\", \"b\"), list(\"a\", \"b\"));\n```\n\nIn this structure, the pairs in the two list(\"a\", \"b\") lists are distinct, although they contain the same strings.\n\nWhen thought of as a list, and both represent the same list:\n\n```javascript\nabab\n [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ]\n\nlist(list(\"a\", \"b\"), \"a\", \"b\")\n\nlist(list(\"a\", \"b\"), \"a\", \"b\");\n```\n\nIn general, sharing is completely undetectable if we operate on lists using\nonly\npair,\nhead,\nand\ntail.\n\nHowever, if we allow mutators on list structure, sharing becomes\nsignificant.\n\nAs an example of the difference that sharing can make,\nconsider the following\nfunction,\nwhich modifies the\nhead\nof the structure to which it is applied:\n\n```javascript\nset_to_wow\n\nfunction set_to_wow(x) {\n set_head(head(x), \"wow\");\n return x;\n}\n```\n\nEven though and\nare the same structure,\napplying\nset_to_wow\nto them yields different results.\n\nWith ,\naltering the\nhead\nalso changes the\ntail,\nbecause in the\nhead\nand the\ntail\nare the same pair.\n\nWith , the\nhead\nand\ntail\nare distinct, so\nset_to_wow\nmodifies only the\nhead:\n\n```javascript\nset_to_wow_example_1\n pair_example1\n [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ]\n\nz1;\n\n[[\"a\", [\"b\", null]], [\"a\", [\"b\", null]]]\n```\n\n```javascript\nset_to_wow_example_2\n set_to_wow\n pair_example1\n [ [ 'wow', [ 'b', null ] ], [ 'wow', [ 'b', null ] ] ]\n\nset_to_wow(z1);\n\n[[\"wow\", [\"b\", null]], [\"wow\", [\"b\", null]]]\n```\n\n```javascript\nset_to_wow_example_3\n pair_example2\n [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ]\n\nz2;\n\n >\n[[\"a\", [\"b\", null]], [\"a\", [\"b\", null]]]\n```\n\n```javascript\nset_to_wow_example_4\n set_to_wow\n pair_example2\n [ [ 'wow', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ]\n\nset_to_wow(z2);\n\n[[\"wow\", [\"b\", null]], [\"a\", [\"b\", null]]]\n```", + "token_count": 308, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3430,8 +4690,8 @@ "chunk_id": "Modularity_Objects_and_State_Mutable_List_Structure_3" }, { - "content": "is false.\n\nAs will be seen in the following sections, we can exploit sharing to\ngreatly extend the repertoire of data structures that can be\nrepresented by pairs.\n\nOn the other hand, sharing can also be\nset_head\nand\nset_tail\nshould be used with care; unless we have a good understanding of how our\ndata objects are shared, mutation can have unanticipated\nresults.\n\nWhen we introduced compound data, we observed in section that pairs can be represented purely in terms of functions:\n\n```javascript\ncons_1_2_run\n 1\n\nfunction pair(x, y) {\n function dispatch(m) {\n return m === \"head\"\n ? x\n : m === \"tail\"\n ? y\n : error(m, \"undefined operation -- pair\");\n }\n return dispatch;\n}\n\nfunction head(z) { return z(\"head\"); }\n\nfunction tail(z) { return z(\"tail\"); }\n```\n\nThe same observation is true for mutable data.\n\nWe can implement\nmutable data objects as\nfunctions\nusing assignment and local state.\n\nFor instance, we can extend the above\npair implementation to handle\nset_head\nand\nset_tail\nin a manner analogous to the way we implemented bank accounts using\nmake_account\nin section :\n\n```javascript\ncons_1_2_run_3\n\nconst x = pair(1, 2);\nset_head(x, 3);\nhead(x);\n```\n\n```javascript\npair2\n cons_1_2_run_3\n 3\n\nfunction pair(x, y) {\n function set_x(v) { x = v; }\n function set_y(v) { y = v; }\n return m => m === \"head\"\n ? x\n : m === \"tail\"\n ? y\n : m === \"set_head\"\n ? set_x\n : m === \"set_tail\"\n ? set_y\n : error(m, \"undefined operation -- pair\");\n}\n\nfunction head(z) { return z(\"head\"); }\n\nfunction tail(z) { return z(\"tail\"); }\n\nfunction set_head(z, new_value) {\n z(\"set_head\")(new_value);\n return z;\n}\nfunction set_tail(z, new_value) {\n z(\"set_tail\")(new_value);\n return z;\n}\n```\n\nAssignment is all that is needed, theoretically, to account for the\nbehavior of mutable data.", - "token_count": 287, + "content": "With , the\nhead\nand\ntail\nare distinct, so\nset_to_wow\nmodifies only the\nhead:\n\nWhen applied to two nonprimitive values, x === y tests whether and are the same object (that is, whether and are equal as pointers).\n\nThus, with and\nas defined in\nfigure and,\nhead(z1) === tail(z1)\nis true and\nhead(z2) === tail(z2)\nis false.\n\nAs will be seen in the following sections, we can exploit sharing to\ngreatly extend the repertoire of data structures that can be\nrepresented by pairs.\n\nOn the other hand, sharing can also be\ndangerous, since modifications made to structures will also affect\nother structures that happen to share the modified parts.\n\nThe mutation\noperations\nset_head\nand\nset_tail\nshould be used with care; unless we have a good understanding of how our\ndata objects are shared, mutation can have unanticipated\nresults.\n\nWhen we introduced compound data, we observed in section that pairs can be represented purely in terms of functions:\n\n```javascript\ncons_1_2_run\n 1\n\nfunction pair(x, y) {\n function dispatch(m) {\n return m === \"head\"\n ? x\n : m === \"tail\"\n ? y\n : error(m, \"undefined operation -- pair\");\n }\n return dispatch;\n}\n\nfunction head(z) { return z(\"head\"); }\n\nfunction tail(z) { return z(\"tail\"); }\n```\n\nThe same observation is true for mutable data.\n\nWe can implement\nmutable data objects as\nfunctions\nusing assignment and local state.\n\nFor instance, we can extend the above\npair implementation to handle\nset_head\nand\nset_tail\nin a manner analogous to the way we implemented bank accounts using\nmake_account\nin section :\n\n```javascript\ncons_1_2_run_3\n\nconst x = pair(1, 2);\nset_head(x, 3);\nhead(x);\n```", + "token_count": 264, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3440,8 +4700,8 @@ "chunk_id": "Modularity_Objects_and_State_Mutable_List_Structure_4" }, { - "content": "Assignment is all that is needed, theoretically, to account for the\nbehavior of mutable data.\n\nAs soon as we admit\nassignment\nto our language, we raise all the issues, not only of assignment, but of\nmutable data in general.", - "token_count": 39, + "content": "For instance, we can extend the above\npair implementation to handle\nset_head\nand\nset_tail\nin a manner analogous to the way we implemented bank accounts using\nmake_account\nin section :\n\nAssignment is all that is needed, theoretically, to account for the\nbehavior of mutable data.\n\nAs soon as we admit\nassignment\nto our language, we raise all the issues, not only of assignment, but of\nmutable data in general.", + "token_count": 69, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3450,8 +4710,8 @@ "chunk_id": "Modularity_Objects_and_State_Mutable_List_Structure_5" }, { - "content": "Computer programs are traditionally organized as\none-directional computations, which perform operations on prespecified\narguments to produce desired outputs.\n\nOn the other hand, we often\nmodel systems in terms of relations among quantities.\n\nFor example, a\nmathematical model of a mechanical structure might include the\ninformation that the deflection $d$ of a metal\nrod is related to the force $F$ on the rod, the\nlength $L$ of the rod, the cross-sectional\narea $A$ , and the elastic modulus\n$E$ via the equation\n\\[\n\\begin{array}{lll}\nd A E & = & F L\n\\end{array}\n\\]\nSuch an equation is not one-directional.\n\nGiven any four of the\nquantities, we can use it to compute the fifth.\n\nYet translating the\nequation into a traditional computer language would force us to choose\none of the quantities to be computed in terms of the other four.\n\nThus, a\nfunction\nfor computing the area $A$ could not be used to\ncompute the deflection $d$ , even though the\ncomputations of $A$ and\n$d$ arise from the same\nequation.\n\nIn this section, we sketch the design of a language that enables us to work\nin terms of\nprimitive constraints , which state that certain relations hold\nbetween quantities.\n\nFor example,\nadder(a, b, c)\nspecifies that the quantities $a$ ,\n$b$ , and $c$ must be\nrelated by the equation $a+b=c$ ,\nmultiplier(x, y, z)\nexpresses the constraint $xy = z$ , and\nconstant(3.14, x)\nsays that the value of $x$ must be 3.14.\n\nOur language provides a means of combining primitive constraints in order to\nexpress more complex relations.\n\nWe combine constraints by constructing\nconstraint networks , in which constraints are joined by\nconnectors.\n\nA connector is an object that holds a\nvalue that may participate in one or more constraints.", - "token_count": 293, + "content": "Computer programs are traditionally organized as\none-directional computations, which perform operations on prespecified\narguments to produce desired outputs.\n\nOn the other hand, we often\nmodel systems in terms of relations among quantities.\n\nFor example, a\nmathematical model of a mechanical structure might include the\ninformation that the deflection $d$ of a metal\nrod is related to the force $F$ on the rod, the\nlength $L$ of the rod, the cross-sectional\narea $A$ , and the elastic modulus\n$E$ via the equation\n\\[\n\\begin{array}{lll}\nd A E & = & F L\n\\end{array}\n\\]\nSuch an equation is not one-directional.\n\nGiven any four of the\nquantities, we can use it to compute the fifth.\n\nYet translating the\nequation into a traditional computer language would force us to choose\none of the quantities to be computed in terms of the other four.\n\nThus, a\nfunction\nfor computing the area $A$ could not be used to\ncompute the deflection $d$ , even though the\ncomputations of $A$ and\n$d$ arise from the same\nequation.\n\nIn this section, we sketch the design of a language that enables us to work\nin terms of\nrelations themselves.\n\nThe primitive elements of the language\nare\nprimitive constraints , which state that certain relations hold\nbetween quantities.\n\nFor example,\nadder(a, b, c)\nspecifies that the quantities $a$ ,\n$b$ , and $c$ must be\nrelated by the equation $a+b=c$ ,\nmultiplier(x, y, z)\nexpresses the constraint $xy = z$ , and\nconstant(3.14, x)\nsays that the value of $x$ must be 3.14.\n\nOur language provides a means of combining primitive constraints in order to\nexpress more complex relations.\n\nWe combine constraints by constructing\nconstraint networks , in which constraints are joined by\nconnectors.", + "token_count": 285, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3460,8 +4720,8 @@ "chunk_id": "Modularity_Objects_and_State_Propagation_of_Constraints_1" }, { - "content": "A connector is an object that holds a\nvalue that may participate in one or more constraints.\n\nFor example, we know\nthat the relationship between Fahrenheit and Celsius temperatures is\n\\[\n\\begin{array}{lll}\n9C & = & 5(F - 32)\n\\end{array}\n\\]\nSuch a constraint can be thought of as a network consisting of primitive\nadder, multiplier, and constant constraints\n(figure ).\n\nIn the figure, we see on the\nleft a multiplier box with three terminals, labeled\n$m_1$ , $m_2$ , and\n$p$.\n\nThese connect the multiplier to the rest of\nthe network as follows:\nThe $m_1$ terminal is linked to a connector\n$C$ , which will hold the Celsius temperature.\n\nThe $m_2$ terminal is linked to a connector\n$w$ , which is also linked to a constant box that\nholds 9.\n\nThe $p$ terminal, which the multiplier\nbox constrains to be the product of $m_1$ and\n$m_2$ , is linked to the\n$p$ terminal of another multiplier box, whose\n$m_2$ is connected to a constant 5 and whose\n$m_1$ is connected to one of the terms in a sum.\n\nThe relation $9C = 5(F - 32)$\nexpressed as a constraint network.\n\nComputation by such a network proceeds as follows: When a connector is\ngiven a value (by the user or by a constraint box to which it is\nlinked), it awakens all of its associated constraints (except for the\nconstraint that just awakened it) to inform them that it has a value.\n\nEach awakened constraint box then polls its connectors to see if there\nis enough information to determine a value for a connector.\n\nIf so,\nthe box sets that connector, which then awakens all of its associated\nconstraints, and so on.", - "token_count": 282, + "content": "We combine constraints by constructing\nconstraint networks , in which constraints are joined by\nconnectors.\n\nFor example, we know\nthat the relationship between Fahrenheit and Celsius temperatures is\n\\[\n\\begin{array}{lll}\n9C & = & 5(F - 32)\n\\end{array}\n\\]\nSuch a constraint can be thought of as a network consisting of primitive\nadder, multiplier, and constant constraints\n(figure ).\n\nIn the figure, we see on the\nleft a multiplier box with three terminals, labeled\n$m_1$ , $m_2$ , and\n$p$.\n\nThese connect the multiplier to the rest of\nthe network as follows:\nThe $m_1$ terminal is linked to a connector\n$C$ , which will hold the Celsius temperature.\n\nThe $m_2$ terminal is linked to a connector\n$w$ , which is also linked to a constant box that\nholds 9.\n\nThe $p$ terminal, which the multiplier\nbox constrains to be the product of $m_1$ and\n$m_2$ , is linked to the\n$p$ terminal of another multiplier box, whose\n$m_2$ is connected to a constant 5 and whose\n$m_1$ is connected to one of the terms in a sum.\n\nThe relation $9C = 5(F - 32)$\nexpressed as a constraint network.\n\nComputation by such a network proceeds as follows: When a connector is\ngiven a value (by the user or by a constraint box to which it is\nlinked), it awakens all of its associated constraints (except for the\nconstraint that just awakened it) to inform them that it has a value.\n\nEach awakened constraint box then polls its connectors to see if there\nis enough information to determine a value for a connector.\n\nIf so,\nthe box sets that connector, which then awakens all of its associated\nconstraints, and so on.", + "token_count": 280, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3470,8 +4730,8 @@ "chunk_id": "Modularity_Objects_and_State_Propagation_of_Constraints_2" }, { - "content": "If so,\nthe box sets that connector, which then awakens all of its associated\nconstraints, and so on.\n\nFor instance, in conversion between\nCelsius and Fahrenheit, $w$ ,\n$x$ , and $y$ are\nimmediately set by the constant boxes to $9$, $5$, and $32$, respectively.\n\nThe\nconnectors awaken the multipliers and the adder, which determine that there\nis not enough information to proceed.\n\nIf the user (or some other part of\nthe network) sets $C$ to a value (say 25), the\nleftmost multiplier will be awakened, and it will set\n$u$ to $25\\cdot 9=225$.\n\nThen $u$ awakens the second multiplier, which sets\n$v$ to $45$, and $v$\nawakens the adder, which sets $F$ to $77$.\n\n```javascript\nTo use the constraint system to carry out the temperature computation\n\toutlined above, we first call the constructor\n\tmake_connector\n\tto create two connectors,\n```\n\n```javascript\ncelsius_fahrenheit_converter_example\n celsius_fahrenheit_converter\n 'ok'\n\nconst C = make_connector();\nconst F = make_connector();\ncelsius_fahrenheit_converter(C, F);\n```\n\nThe function that creates the network is defined as follows:\n\n```javascript\ncelsius_fahrenheit_converter\n make_connector\n multiplier_2\n adder\n constant\n celsius_fahrenheit_converter_example\n 'ok'\n\nfunction celsius_fahrenheit_converter(c, f) {\n const u = make_connector();\n const v = make_connector();\n const w = make_connector();\n const x = make_connector();\n const y = make_connector();\n multiplier(c, w, u);\n multiplier(v, x, u);\n adder(v, y, f);\n constant(9, w);\n constant(5, x);\n constant(32, y);\n return \"ok\";\n}\n```\n\nThis function creates the internal connectors using the primitive constraint constructors , expressing these combinations of primitive elements in terms of functions automatically provides our\n\nlanguage with a means of abstraction for compound objects.\n\nTo watch the network in action, we can place probes on the connectors\nfunction\nsimilar to the one we used to monitor wires in\nsection.\n\nPlacing a probe on a\nconnector will cause a message to be printed whenever the connector is\ngiven a value:\n\n```javascript\ncelsius_probe\n probe_2\n celsius_fahrenheit_converter_example\n\nprobe(\"Celsius temp\", C);\nprobe(\"Fahrenheit temp\", F);\n```", - "token_count": 306, + "content": "If so,\nthe box sets that connector, which then awakens all of its associated\nconstraints, and so on.\n\nThe\nconnectors awaken the multipliers and the adder, which determine that there\nis not enough information to proceed.\n\nIf the user (or some other part of\nthe network) sets $C$ to a value (say 25), the\nleftmost multiplier will be awakened, and it will set\n$u$ to $25\\cdot 9=225$.\n\nThen $u$ awakens the second multiplier, which sets\n$v$ to $45$, and $v$\nawakens the adder, which sets $F$ to $77$.\n\nTo use the constraint system to carry out the temperature computation outlined above, we first call the constructor make_connector to create two connectors, and ,\n\nand then link them in an appropriate network:\n\n```javascript\ncelsius_fahrenheit_converter_example\n celsius_fahrenheit_converter\n 'ok'\n\nconst C = make_connector();\nconst F = make_connector();\ncelsius_fahrenheit_converter(C, F);\n\n\"ok\"\n```\n\nThe function that creates the network is defined as follows:\n\n```javascript\ncelsius_fahrenheit_converter\n make_connector\n multiplier_2\n adder\n constant\n celsius_fahrenheit_converter_example\n 'ok'\n\nfunction celsius_fahrenheit_converter(c, f) {\n const u = make_connector();\n const v = make_connector();\n const w = make_connector();\n const x = make_connector();\n const y = make_connector();\n multiplier(c, w, u);\n multiplier(v, x, u);\n adder(v, y, f);\n constant(9, w);\n constant(5, x);\n constant(32, y);\n return \"ok\";\n}\n```\n\nThis\nfunction\ncreates the internal connectors ,\n, ,\n, and , and\nlinks them as shown in figure using the\nprimitive constraint constructors ,\n, and.\n\nJust as with the digital-circuit\nsimulator of section , expressing\nthese combinations of primitive elements in terms of\nfunctions\nautomatically provides our language with a means of abstraction for compound\nobjects.\n\nTo watch the network in action, we can place probes on the connectors\nand , using a\nfunction\nsimilar to the one we used to monitor wires in\nsection.", + "token_count": 285, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3480,8 +4740,8 @@ "chunk_id": "Modularity_Objects_and_State_Propagation_of_Constraints_3" }, { - "content": "Placing a probe on a\nconnector will cause a message to be printed whenever the connector is\ngiven a value:\n\nNext we set the value of set_value tells\n\n```javascript\nset_value_example\n has_value\n celsius_probe\n 'done'\n\nset_value(C, 25, \"user\");\n```\n\nThe probe on\n\nNow we can try to set\n\n```javascript\nset_value_example_2\n set_value_example\n\nset_value(F, 212, \"user\");\n```\n\nThe connector complains that it has sensed a contradiction: Its value\nis 77, and someone is trying to set it to 212.\n\nIf we really want to\nreuse the network with new values, we can tell\n\n```javascript\nforget_value_example\n set_value_example\n 'done'\n\nforget_value(C, \"user\");\n```\n\n\"user\",\nwho set its value originally, is now retracting that value, so\n77.\n\nThus,\n\nNow that\n\n```javascript\nset_value_example_3\n forget_value_example\n 'done'\n\nset_value(F, 212, \"user\");\n```\n\nThis new value, when propagated through the network, forces\n\nThe constraint system is implemented via procedural objects with local\nstate, in a manner very similar to the digital-circuit simulator of\nsection.\n\nAlthough the primitive\nobjects of the constraint system are somewhat more complex, the overall\nsystem is simpler, since there is no concern about agendas and logic delays.\n\nThe basic\n-\n-\nhas_value(connector)\ntells whether the connector has a value.\n-\n-\nget_value(connector)\nreturns the connector s current value.\n-\n-\nset_value(connector, new-value, informant)\nindicates that the informant is requesting the connector to set its\nvalue to the new value.\n-\n-\nforget_value(connector, retractor)\ntells the connector that the retractor is requesting it to forget its\nvalue.\n-\n-\nconnect(connector, new-constraint)\ntells the connector to participate in the new constraint.\n\nThe connectors communicate with the constraints by means of the\nfunctions\ninform_@about_@value,\nwhich tells the given constraint that the connector has a value, and\ninform_@about_@no_@value,\nwhich tells the constraint that the connector has lost its value.\n\nfunction with local state (the function", - "token_count": 292, + "content": "To watch the network in action, we can place probes on the connectors\nand , using a\nfunction\nsimilar to the one we used to monitor wires in\nsection.\n\n```javascript\ncelsius_probe\n probe_2\n celsius_fahrenheit_converter_example\n\nprobe(\"Celsius temp\", C);\nprobe(\"Fahrenheit temp\", F);\n```\n\nNext we set the value of to 25.\n\n(The third\nargument to\nset_value\ntells that this directive comes from the.)\n\n```javascript\nset_value_example\n has_value\n celsius_probe\n 'done'\n\nset_value(C, 25, \"user\");\n\n\"Probe: Celsius temp = 25\"\n\"Probe: Fahrenheit temp = 77\"\n\"done\"\n```\n\nThe probe on awakens and reports the value.\nalso\npropagates its value through the network as described above.\n\nThis\nsets to 77, which is reported by the probe\non.\n\nNow we can try to set to a new value, say 212:\n\n```javascript\nset_value_example_2\n set_value_example\n\nset_value(F, 212, \"user\");\n\n\"Error! Contradiction: (77, 212)\"\n```\n\nThe connector complains that it has sensed a contradiction: Its value\nis 77, and someone is trying to set it to 212.\n\nIf we really want to\nreuse the network with new values, we can tell\nto forget its old value:\n\n```javascript\nforget_value_example\n set_value_example\n 'done'\n\nforget_value(C, \"user\");\n\n\"Probe: Celsius temp = ?\"\n\"Probe: Fahrenheit temp = ?\"\n\"done\"\n```\n\nfinds that the\n\"user\",\nwho set its value originally, is now retracting that value, so\nagrees to lose its value, as shown by the\nprobe, and informs the rest of the network of this fact.\n\nThis information\neventually propagates to , which now finds\nthat it has no reason for continuing to believe that its own\nvalue is 77.\n\nThus, also\ngives up its value, as shown by the probe.\n\nNow that has no value, we are free to set it to 212:\n\n```javascript\nset_value_example_3\n forget_value_example\n 'done'\n\nset_value(F, 212, \"user\");\n\n\"Probe: Fahrenheit temp = 212\"\n\"Probe: Celsius temp = 100\"\n\"done\"\n```", + "token_count": 293, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3490,9 +4750,9 @@ "chunk_id": "Modularity_Objects_and_State_Propagation_of_Constraints_4" }, { - "content": "function with local state (the function\n\n```javascript\nadder\n has_value\n\nfunction adder(a1, a2, sum) {\n function process_new_value() {\n if (has_value(a1) && has_value(a2)) {\n set_value(sum, get_value(a1) + get_value(a2), me);\n } else if (has_value(a1) && has_value(sum)) {\n set_value(a2, get_value(sum) - get_value(a1), me);\n } else if (has_value(a2) && has_value(sum)) {\n set_value(a1, get_value(sum) - get_value(a2), me);\n } else {}\n }\n function process_forget_value() {\n forget_value(sum, me);\n forget_value(a1, me);\n forget_value(a2, me);\n process_new_value();\n }\n function me(request) {\n if (request === \"I have a value.\") {\n process_new_value();\n } else if (request === \"I lost my value.\") {\n process_forget_value();\n } else {\n error(request, \"unknown request -- adder\");\n }\n }\n connect(a1, me);\n connect(a2, me);\n connect(sum, me);\n return me;\n}\n```\n\nThe function\nconnects the new adder to the designated\nconnectors and returns it as its value.\n\nThe\nfunction\nfunctions.\n\nThe following\nsyntax interfaces (see\nfootnote in\nsection ) are used in conjunction\nwith the dispatch:\n\n```javascript\ninform_about_value\n\nfunction inform_about_value(constraint) {\n return constraint(\"I have a value.\");\n}\n\nfunction inform_about_no_value(constraint) {\n return constraint(\"I lost my value.\");\n}\n```\n\nThe adder s local\nfunction\nprocess_new_value\nis called when the adder is informed that one of its connectors has a value.\n\nThe adder first checks to see if both\nset_value\nis\nprocess_new_value.\n\nThe reason for this last step is that one or more connectors may still\nhave a value (that is, a connector may have had a value that was not\noriginally set by the adder), and these values may need to be\npropagated back through the adder.\n\nA multiplier is very similar to an adder.\n\nIt will set its", - "token_count": 255, - "has_code": true, + "content": "Now that has no value, we are free to set it to 212:\n\nNotice that the\nvery same network is being used to compute\ngiven and to compute\ngiven.\n\nThis nondirectionality of computation is the distinguishing feature of\nconstraint-based systems.\n\nThe constraint system is implemented via procedural objects with local\nstate, in a manner very similar to the digital-circuit simulator of\nsection.\n\nAlthough the primitive\nobjects of the constraint system are somewhat more complex, the overall\nsystem is simpler, since there is no concern about agendas and logic delays.\n\nThe basic\noperations on connectors are the following:\n-\n-\nhas_value(connector)\ntells whether the connector has a value.\n-\n-\nget_value(connector)\nreturns the connector s current value.\n-\n-\nset_value(connector, new-value, informant)\nindicates that the informant is requesting the connector to set its\nvalue to the new value.\n-\n-\nforget_value(connector, retractor)\ntells the connector that the retractor is requesting it to forget its\nvalue.\n-\n-\nconnect(connector, new-constraint)\ntells the connector to participate in the new constraint.\n\nThe connectors communicate with the constraints by means of the\nfunctions\ninform_@about_@value,\nwhich tells the given constraint that the connector has a value, and\ninform_@about_@no_@value,\nwhich tells the constraint that the connector has lost its value.\n\nconstructs an adder constraint among\nsummand connectors and\nand a\nconnector.\n\nAn adder is implemented as a\nfunction\nwith local state (the\nfunction\nbelow):", + "token_count": 227, + "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", "subsection": "Propagation of Constraints", @@ -3500,8 +4760,8 @@ "chunk_id": "Modularity_Objects_and_State_Propagation_of_Constraints_5" }, { - "content": "It will set its\n\n```javascript\nmultiplier_2\n has_value\n\nfunction multiplier(m1, m2, product) {\n function process_new_value() {\n if ((has_value(m1) && get_value(m1) === 0)\n || (has_value(m2) && get_value(m2) === 0)) {\n set_value(product, 0, me);\n } else if (has_value(m1) && has_value(m2)) {\n set_value(product, get_value(m1) * get_value(m2), me);\n } else if (has_value(product) && has_value(m1)) {\n set_value(m2, get_value(product) / get_value(m1), me);\n } else if (has_value(product) && has_value(m2)) {\n set_value(m1, get_value(product) / get_value(m2), me);\n } else {}\n }\n function process_forget_value() {\n forget_value(product, me);\n forget_value(m1, me);\n forget_value(m2, me);\n process_new_value();\n }\n function me(request) {\n if (request === \"I have a value.\") {\n process_new_value();\n } else if (request === \"I lost my value.\") {\n process_forget_value();\n } else {\n error(request, \"unknown request -- multiplier\");\n }\n }\n connect(m1, me);\n connect(m2, me);\n connect(product, me);\n return me;\n}\n```\n\nA \"I have a value.\" or \"I lost my value.\" message sent to the constant box will produce an error.\n\n```javascript\nconstant\n has_value\n\nfunction constant(value, connector) {\n function me(request) {\n error(request, \"unknown request -- constant\");\n }\n connect(connector, me);\n set_value(connector, value, me);\n return me;\n}\n```\n\nFinally, a probe prints a message about the setting or unsetting of the designated connector:\n\n```javascript\nredefine_display\n\nconst display = x => x;\n```\n\n```javascript\nprobe_2\n redefine_display\n has_value\n\nfunction probe(name, connector) {\n function print_probe(value) {\n display(\"Probe: \" + name + \" = \" + stringify(value));\n }\n function process_new_value() {\n print_probe(get_value(connector));\n }\n function process_forget_value() {\n print_probe(\"?\");\n }\n function me(request) {\n return request === \"I have a value.\"\n ? process_new_value()\n : request === \"I lost my value.\"\n ? process_forget_value()\n : error(request, \"unknown request -- probe\");\n }\n connect(connector, me);\n return me;\n}\n```\n\nA connector is represented as a procedural object with local state variables s value; and", - "token_count": 275, + "content": "An adder is implemented as a\nfunction\nwith local state (the\nfunction\nbelow):\n\nThe function\nconnects the new adder to the designated\nconnectors and returns it as its value.\n\nThe\nfunction\n, which represents the adder, acts as a\ndispatch to the local\nfunctions.\n\nThe following\nsyntax interfaces (see\nfootnote in\nsection ) are used in conjunction\nwith the dispatch:\n\n```javascript\ninform_about_value\n\nfunction inform_about_value(constraint) {\n return constraint(\"I have a value.\");\n}\n\nfunction inform_about_no_value(constraint) {\n return constraint(\"I lost my value.\");\n}\n```\n\nThe adder s local\nfunction\nprocess_new_value\nis called when the adder is informed that one of its connectors has a value.\n\nThe adder first checks to see if both and\nhave values.\n\nIf so, it tells\nto set its value to the sum of the two\naddends.\n\nThe argument to\nset_value\nis , which is the adder object itself.\n\nIf\nand do not\nboth have values, then the adder checks to see if perhaps\nand have\nvalues.\n\nIf so, it sets to the difference of\nthese two.\n\nFinally, if and\nhave values, this gives the adder enough\ninformation to set.\n\nIf the adder is told\nthat one of its connectors has lost a value, it requests that all of its\nconnectors now lose their values.\n\n(Only those values that were set by\nthis adder are actually lost.) Then it runs\nprocess_new_value.\n\nThe reason for this last step is that one or more connectors may still\nhave a value (that is, a connector may have had a value that was not\noriginally set by the adder), and these values may need to be\npropagated back through the adder.\n\nA multiplier is very similar to an adder.\n\nIt will set its\nto 0 if either of the factors is 0,\neven if the other factor is not known.", + "token_count": 297, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3510,8 +4770,8 @@ "chunk_id": "Modularity_Objects_and_State_Propagation_of_Constraints_6" }, { - "content": "A connector is represented as a procedural object with local state variables s value; and\n\n```javascript\nmake_connector\n for_each_except\n inform_about_value\n has_value\n\nfunction make_connector() {\n let value = false;\n let informant = false;\n let constraints = null;\n function set_my_value(newval, setter) {\n if (!has_value(me)) {\n value = newval;\n informant = setter;\n return for_each_except(setter,\n inform_about_value,\n constraints);\n } else if (value !== newval) {\n error(list(value, newval), \"contradiction\");\n } else {\n return \"ignored\";\n }\n }\n function forget_my_value(retractor) {\n if (retractor === informant) {\n informant = false;\n return for_each_except(retractor,\n inform_about_no_value,\n constraints);\n } else {\n return \"ignored\";\n }\n }\n function connect(new_constraint) {\n if (is_null(member(new_constraint, constraints))) {\n constraints = pair(new_constraint, constraints);\n } else {}\n if (has_value(me)) {\n inform_about_value(new_constraint);\n } else {}\n return \"done\";\n }\n function me(request) {\n if (request === \"has_value\") {\n return informant !== false;\n } else if (request === \"value\") {\n return value;\n } else if (request === \"set_value\") {\n return set_my_value;\n } else if (request === \"forget\") {\n return forget_my_value;\n } else if (request === \"connect\") {\n return connect;\n } else {\n error(request, \"unknown operation -- connector\");\n }\n }\n return me;\n}\n```\n\nThe connector s local\nfunction\nset_my_value\nis called when there is a request to set the connector s value.\n\nIf\nthe connector does not currently have a value, it will set its value and\nremember as\nfunction\nto all items in a list except a given one:\n\n```javascript\nfor_each_except\n\nfunction for_each_except(exception, fun, list) {\n function loop(items) {\n if (is_null(items)) {\n return \"done\";\n } else if (head(items) === exception) {\n return loop(tail(items));\n } else {\n fun(head(items));\n return loop(tail(items));\n }\n }\n return loop(list);\n}\n```\n\nIf a connector is asked to forget its value, it runs\n\n```javascript\nforget_my_value,\n\ta local function that\n```\n\nfirst checks to make sure that the request is coming from the same\nobject that set the value originally.", - "token_count": 297, + "content": "It will set its\nto 0 if either of the factors is 0,\neven if the other factor is not known.\n\nA constructor simply sets the value of\nthe designated connector.\n\nAny\n\"I have a value.\"\nor\n\"I lost my value.\"\nmessage sent to the constant box will produce an error.\n\n```javascript\nconstant\n has_value\n\nfunction constant(value, connector) {\n function me(request) {\n error(request, \"unknown request -- constant\");\n }\n connect(connector, me);\n set_value(connector, value, me);\n return me;\n}\n```\n\nFinally, a probe prints a message about the setting or unsetting of the designated connector:\n\n```javascript\nredefine_display\n\nconst display = x => x;\n```\n\n```javascript\nprobe_2\n redefine_display\n has_value\n\nfunction probe(name, connector) {\n function print_probe(value) {\n display(\"Probe: \" + name + \" = \" + stringify(value));\n }\n function process_new_value() {\n print_probe(get_value(connector));\n }\n function process_forget_value() {\n print_probe(\"?\");\n }\n function me(request) {\n return request === \"I have a value.\"\n ? process_new_value()\n : request === \"I lost my value.\"\n ? process_forget_value()\n : error(request, \"unknown request -- probe\");\n }\n connect(connector, me);\n return me;\n}\n```\n\nA connector is represented as a procedural object with local state variables , the current value of the connector; , the object that set the\n\nconnector s value; and , a list of the constraints in which the connector participates.", + "token_count": 206, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3520,8 +4780,8 @@ "chunk_id": "Modularity_Objects_and_State_Propagation_of_Constraints_7" }, { - "content": "first checks to make sure that the request is coming from the same\nobject that set the value originally.\n\nIf so, the connector informs its\nassociated constraints about the loss of the value.\n\nThe local function Then, if the connector has a value, it informs the new constraint of this fact.\n\nThe connector s\nfunction\nfunctions\nand also represents the connector as an object.\n\nThe following\nfunctions\nprovide a syntax interface for the dispatch:\n\n```javascript\nhas_value\n\nfunction has_value(connector) {\n return connector(\"has_value\");\n}\nfunction get_value(connector) {\n return connector(\"value\");\n}\nfunction set_value(connector, new_value, informant) {\n return connector(\"set_value\")(new_value, informant);\n}\nfunction forget_value(connector, retractor) {\n return connector(\"forget\")(retractor);\n}\n\nfunction connect(connector, new_constraint) {\n return connector(\"connect\")(new_constraint);\n}\n```", - "token_count": 112, + "content": "connector s value; and , a list of the constraints in which the connector participates.\n\nThe connector s local\nfunction\nset_my_value\nis called when there is a request to set the connector s value.\n\nIf\nthe connector does not currently have a value, it will set its value and\nremember as the constraint that\nrequested the value to be set.\n\nThen the connector will\nnotify all of its participating constraints except the constraint that\nrequested the value to be set.\n\nThis is accomplished using the following\niterator, which applies a designated\nfunction\nto all items in a list except a given one:\n\n```javascript\nfor_each_except\n\nfunction for_each_except(exception, fun, list) {\n function loop(items) {\n if (is_null(items)) {\n return \"done\";\n } else if (head(items) === exception) {\n return loop(tail(items));\n } else {\n fun(head(items));\n return loop(tail(items));\n }\n }\n return loop(list);\n}\n```\n\nIf a connector is asked to forget its value, it runs\nforget_my_value, a local function that\nfirst checks to make sure that the request is coming from the same\nobject that set the value originally.\n\nIf so, the connector informs its\nassociated constraints about the loss of the value.\n\nThe local\nfunction\nadds the designated new constraint\nto the list of constraints if it is not already in that\nlist.\n\nThen, if the connector has a value, it informs the new constraint of this\nfact.\n\nThe connector s\nfunction\nserves as a dispatch to the other internal\nfunctions\nand also represents the connector as an object.\n\nThe following\nfunctions\nprovide a syntax interface for the dispatch:\n\n```javascript\nhas_value\n\nfunction has_value(connector) {\n return connector(\"has_value\");\n}\nfunction get_value(connector) {\n return connector(\"value\");\n}\nfunction set_value(connector, new_value, informant) {\n return connector(\"set_value\")(new_value, informant);\n}\nfunction forget_value(connector, retractor) {\n return connector(\"forget\")(retractor);\n}\n\nfunction connect(connector, new_constraint) {\n return connector(\"connect\")(new_constraint);\n}\n```", + "token_count": 292, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3530,9 +4790,9 @@ "chunk_id": "Modularity_Objects_and_State_Propagation_of_Constraints_8" }, { - "content": "The mutators\nset_head\nand\nset_tail\nenable us to use pairs to construct data structures that cannot be built\nwith\npair,\nhead,\nand\ntail\nalone.\n\nThis section shows how to use pairs to represent a data structure\ncalled a queue.\n\nSection will show how to\nrepresent data structures called tables.\n\nA queue is a sequence in which items are inserted at one end\n(called the\nrear of the queue) and deleted from the other end (the\nfront ).\n\nFigure\nshows an initially empty queue in which the items\nFIFO (first in, first out) buffer.\n\n```javascript\nOperation\n\n\t\tResulting Queue\n\n\t\tconst q = make_queue();\n\n\t\tinsert_queue(q, \"a\");\n\n\t\ta\n\n\t\tinsert_queue(q, \"b\");\n\n\t\ta b\n\n\t\tdelete_queue(q);\n\n\t\tb\n\n\t\tinsert_queue(q, \"c\");\n\n\t\tb c\n\n\t\tinsert_queue(q, \"d\");\n\n\t\tb c d\n\n\t\tdelete_queue(q);\n\n\t\tc d\n\n Queue operations.\n```\n\nIn terms of - - a constructor: make_queue() returns an empty queue (a queue containing no items). \\vspace{6pt} - - a predicate: is_empty_queue(queue) tests if\n\nthe queue is empty. \\vspace{6pt} - - a selector: front_queue(queue) returns the object at the front of the queue, signaling an error if the queue\n\nis empty; it does not modify the queue. \\vspace{6pt} - - two mutators: insert_queue(queue, item) inserts \\\\[4pt] delete_queue(queue) removes\n\nBecause a queue is a sequence of items, we could certainly represent\nit as an ordinary list; the front of the queue would be the\nhead\nof the list, inserting an item in the queue would amount to appending\na new element at the end of the list, and deleting an item from the\nqueue would just be taking the\ntail\nof the list.\n\nHowever, this representation is inefficient, because in order\nto insert an item we must scan the list until we reach the end.", - "token_count": 280, - "has_code": true, + "content": "The mutators\nset_head\nand\nset_tail\nenable us to use pairs to construct data structures that cannot be built\nwith\npair,\nhead,\nand\ntail\nalone.\n\nThis section shows how to use pairs to represent a data structure\ncalled a queue.\n\nSection will show how to\nrepresent data structures called tables.\n\nA queue is a sequence in which items are inserted at one end\n(called the\nrear of the queue) and deleted from the other end (the\nfront ).\n\nFigure\nshows an initially empty queue in which the items\nand are\ninserted.\n\nThen is removed,\nand are\ninserted, and is removed.\n\nBecause items are\nalways removed in the order in which they are inserted, a queue is\nsometimes called a\nFIFO (first in, first out) buffer.\n\nOperation Resulting Queue const q = make_queue(); insert_queue(q, \"a\"); a insert_queue(q, \"b\"); a b delete_queue(q); b insert_queue(q, \"c\"); b c insert_queue(q, \"d\"); b c d delete_queue(q); c d Queue operations.\n\nIn terms of data abstraction, we can regard a queue as defined by the following set of operations: - - a constructor: make_queue() returns an\n\nempty queue (a queue containing no items). \\vspace{6pt} - - a predicate: is_empty_queue(queue) tests if the queue is empty. \\vspace{6pt} - - a selector: front_queue(queue)\n\nreturns the object at the front of the queue, signaling an error if the queue is empty; it does not modify the queue. \\vspace{6pt} -\n\n- two mutators: insert_queue(queue, item) inserts the item at the rear of the queue and returns the modified queue as its value. \\\\[4pt] delete_queue(queue) removes\n\nthe item at the front of the queue and returns the modified queue as its value, signaling an error if the queue is empty before\n\nthe deletion.", + "token_count": 281, + "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", "subsection": "Representing Queues", @@ -3540,8 +4800,8 @@ "chunk_id": "Modularity_Objects_and_State_Representing_Queues_1" }, { - "content": "However, this representation is inefficient, because in order\nto insert an item we must scan the list until we reach the end.\n\nSince the\nonly method we have for scanning a list is by successive\ntail\noperations, this scanning requires $\\Theta(n)$\nsteps for a list of $n$ items.\n\nA simple\nmodification to the list representation overcomes this disadvantage by\nallowing the queue operations to be implemented so that they require\n$\\Theta(1)$ steps; that is, so that the number\nof steps needed is independent of the length of the queue.\n\nThe difficulty with the list representation arises from the need to\nscan to find the end of the list.\n\nThe reason we need to scan is that,\nalthough the standard way of representing a list as a chain of pairs\nreadily provides us with a pointer to the beginning of the list, it\ngives us no easily accessible pointer to the end.\n\nThe modification\nthat avoids the drawback is to represent the queue as a list, together\nwith an additional pointer that indicates the final pair in the list.\n\nThat way, when we go to insert an item, we can consult the rear\npointer and so avoid scanning the list.\n\nA queue is represented, then, as a pair of pointers,\nfront_ptr\nand\nrear_ptr,\nwhich indicate, respectively, the first and last pairs in an ordinary list.\n\nSince we would like the queue to be an identifiable object, we can use\npair\nto combine the two pointers.\n\nThus, the queue itself will be the\npair\nof the two pointers.\n\nFigure\nillustrates this representation.\n\nImplementation of a queue as a list with front and rear pointers.\n\nTo define the queue operations we use the following functions, which enable us to select and to modify the front and rear pointers of a\n\nqueue:", - "token_count": 298, + "content": "the deletion.\n\nHowever, this representation is inefficient, because in order\nto insert an item we must scan the list until we reach the end.\n\nSince the\nonly method we have for scanning a list is by successive\ntail\noperations, this scanning requires $\\Theta(n)$\nsteps for a list of $n$ items.\n\nA simple\nmodification to the list representation overcomes this disadvantage by\nallowing the queue operations to be implemented so that they require\n$\\Theta(1)$ steps; that is, so that the number\nof steps needed is independent of the length of the queue.\n\nThe difficulty with the list representation arises from the need to\nscan to find the end of the list.\n\nThe reason we need to scan is that,\nalthough the standard way of representing a list as a chain of pairs\nreadily provides us with a pointer to the beginning of the list, it\ngives us no easily accessible pointer to the end.\n\nThe modification\nthat avoids the drawback is to represent the queue as a list, together\nwith an additional pointer that indicates the final pair in the list.\n\nThat way, when we go to insert an item, we can consult the rear\npointer and so avoid scanning the list.\n\nA queue is represented, then, as a pair of pointers,\nfront_ptr\nand\nrear_ptr,\nwhich indicate, respectively, the first and last pairs in an ordinary list.\n\nSince we would like the queue to be an identifiable object, we can use\npair\nto combine the two pointers.\n\nThus, the queue itself will be the\npair\nof the two pointers.\n\nFigure\nillustrates this representation.\n\nImplementation of a queue as a list with front and rear pointers.\n\nTo define the queue operations we use the following functions, which enable us to select and to modify the front and rear pointers of a\n\nqueue:", + "token_count": 300, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3550,8 +4810,8 @@ "chunk_id": "Modularity_Objects_and_State_Representing_Queues_2" }, { - "content": "queue:\n\n```javascript\nmodify_pointers_example\n\nconst q = pair(1, 2);\nset_front_ptr(q, 42);\nfront_ptr(q);\n```\n\n```javascript\nmodify_pointers\n modify_pointers_example\n 42\n\nfunction front_ptr(queue) { return head(queue); }\n\nfunction rear_ptr(queue) { return tail(queue); }\n\nfunction set_front_ptr(queue, item) { set_head(queue, item); }\n\nfunction set_rear_ptr(queue, item) { set_tail(queue, item); }\n```\n\nNow we can implement the actual queue operations.\n\nWe will consider a\nqueue to be empty if its front pointer is the empty list:\n\n```javascript\nis_empty_queue_example\n\nconst q = pair(null, 2);\nis_empty_queue(q);\n```\n\n```javascript\nmodify_pointers\n is_empty_queue\n is_empty_queue_example\n true\n\nfunction is_empty_queue(queue) { return is_null(front_ptr(queue)); }\n```\n\nThe make_queue constructor returns, as an initially empty queue, a pair whose head and tail are both the empty list:\n\n```javascript\nmake_queue_example\n modify_pointers\n\nconst q = make_queue();\nfront_ptr(q);\n```\n\n```javascript\nmake_queue\n make_queue_example\n null\n\nfunction make_queue() { return pair(null, null); }\n```\n\nTo select the item at the front of the queue, we return the head of the pair indicated by the front pointer:\n\n```javascript\nfront_queue_example\n\nconst q = pair(pair(1, 2), 3);\nfront_queue(q);\n```\n\n```javascript\nfront_queue\n is_empty_queue\n front_queue_example\n 1\n\nfunction front_queue(queue) {\n return is_empty_queue(queue)\n ? error(queue, \"front_queue called with an empty queue\")\n : head(front_ptr(queue));\n}\n```\n\nTo insert an item in a queue, we follow the method whose result is\nindicated in\nfigure.\n\nWe first create a new\npair whose\nhead\nis the item to be inserted and whose\ntail\nis the empty list.\n\nIf the queue was initially empty, we set the front and\nrear pointers of the queue to this new pair.\n\nOtherwise, we modify the\nfinal pair in the queue to point to the new pair, and also set the\nrear pointer to the new pair.\n\n```javascript\nResult of using\n\t insert_queue(q, \"d\") on the\n\t queue of figure.\n```\n\n```javascript\nprint_queue_example\n make_queue\n insert_queue\n delete_queue\n\nconst q1 = make_queue();\ninsert_queue(q1, \"a\");\ninsert_queue(q1, \"b\");\ndelete_queue(q1);\ndelete_queue(q1);\n```", - "token_count": 294, + "content": "queue:\n\n```javascript\nmodify_pointers\n modify_pointers_example\n 42\n\nfunction front_ptr(queue) { return head(queue); }\n\nfunction rear_ptr(queue) { return tail(queue); }\n\nfunction set_front_ptr(queue, item) { set_head(queue, item); }\n\nfunction set_rear_ptr(queue, item) { set_tail(queue, item); }\n```\n\nNow we can implement the actual queue operations.\n\nWe will consider a\nqueue to be empty if its front pointer is the empty list:\n\n```javascript\nis_empty_queue_example\n\nconst q = pair(null, 2);\nis_empty_queue(q);\n```\n\n```javascript\nmodify_pointers\n is_empty_queue\n is_empty_queue_example\n true\n\nfunction is_empty_queue(queue) { return is_null(front_ptr(queue)); }\n```\n\nThe make_queue constructor returns, as an initially empty queue, a pair whose head and tail are both the empty list:\n\n```javascript\nmake_queue_example\n modify_pointers\n\nconst q = make_queue();\nfront_ptr(q);\n```\n\n```javascript\nmake_queue\n make_queue_example\n null\n\nfunction make_queue() { return pair(null, null); }\n```\n\nTo select the item at the front of the queue, we return the head of the pair indicated by the front pointer:\n\n```javascript\nfront_queue_example\n\nconst q = pair(pair(1, 2), 3);\nfront_queue(q);\n```\n\n```javascript\nfront_queue\n is_empty_queue\n front_queue_example\n 1\n\nfunction front_queue(queue) {\n return is_empty_queue(queue)\n ? error(queue, \"front_queue called with an empty queue\")\n : head(front_ptr(queue));\n}\n```\n\nTo insert an item in a queue, we follow the method whose result is\nindicated in\nfigure.\n\nWe first create a new\npair whose\nhead\nis the item to be inserted and whose\ntail\nis the empty list.\n\nIf the queue was initially empty, we set the front and\nrear pointers of the queue to this new pair.\n\nOtherwise, we modify the\nfinal pair in the queue to point to the new pair, and also set the\nrear pointer to the new pair.\n\nResult of using insert_queue(q, \"d\") on the queue of figure.\n\n```javascript\nprint_queue_example\n make_queue\n insert_queue\n delete_queue\n\nconst q1 = make_queue();\ninsert_queue(q1, \"a\");\ninsert_queue(q1, \"b\");\ndelete_queue(q1);\ndelete_queue(q1);\n```", + "token_count": 281, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3560,8 +4820,8 @@ "chunk_id": "Modularity_Objects_and_State_Representing_Queues_3" }, { - "content": "Otherwise, we modify the\nfinal pair in the queue to point to the new pair, and also set the\nrear pointer to the new pair.\n\n```javascript\ninsert_queue\n modify_pointers\n is_empty_queue\n print_queue_example\n [ null, [ 'b', null ] ]\n\nfunction insert_queue(queue, item) {\n const new_pair = pair(item, null);\n if (is_empty_queue(queue)) {\n set_front_ptr(queue, new_pair);\n set_rear_ptr(queue, new_pair);\n } else {\n set_tail(rear_ptr(queue), new_pair);\n set_rear_ptr(queue, new_pair);\n }\n return queue;\n}\n```\n\nTo delete the item at the front of the queue, we merely modify the front pointer so that it now points at the second item\n\nin the queue, which can be found by following the tail pointer of the first item (see figure ):\n\n```javascript\nResult of using delete_queue(q)\n\t on the queue of figure.\n```\n\n```javascript\ndelete_queue\n is_empty_queue\n modify_pointers\n print_queue_example\n [ null, [ 'b', null ] ]\n\nfunction delete_queue(queue) {\n if (is_empty_queue(queue)) {\n error(queue, \"delete_queue called with an empty queue\");\n } else {\n set_front_ptr(queue, tail(front_ptr(queue)));\n return queue;\n }\n}\n```", - "token_count": 155, + "content": "Result of using insert_queue(q, \"d\") on the queue of figure.\n\nTo delete the item at the front of the queue, we merely modify the front pointer so that it now points at the second item\n\nin the queue, which can be found by following the tail pointer of the first item (see figure ): Result of using delete_queue(q) on the\n\nqueue of figure.\n\n```javascript\ndelete_queue\n is_empty_queue\n modify_pointers\n print_queue_example\n [ null, [ 'b', null ] ]\n\nfunction delete_queue(queue) {\n if (is_empty_queue(queue)) {\n error(queue, \"delete_queue called with an empty queue\");\n } else {\n set_front_ptr(queue, tail(front_ptr(queue)));\n return queue;\n }\n}\n```", + "token_count": 98, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3570,8 +4830,8 @@ "chunk_id": "Modularity_Objects_and_State_Representing_Queues_4" }, { - "content": "When we studied various ways of representing sets in chapter , we\nmentioned in section the task of\nmaintaining a table of records\n, we made extensive use of\ntwo-dimensional tables, in which information is stored and retrieved\nusing two keys.\n\nHere we see how to build tables as mutable list\nstructures.\n\nWe first consider a\nheads\npoint to successive records.\n\nThese gluing pairs are called the\nbackbone of the table.\n\nIn order to have a place that we can\nchange when we add a new record to the table, we build the table as a\nheaded list.\n\nA headed list has a special backbone pair at the\nbeginning, which holds a dummy record in this case\nthe arbitrarily chosen\nstring \"*table*\".\n\nFigure\nshows the box-and-pointer diagram for the table\n\n```javascript\na: 1\nb: 2\nc: 3\n```\n\nA table represented as a headed list.\n\nTo extract information from a table we use the\nfunction,\nwhich takes a key as argument and returns the associated value (or\nundefined\nif\nthere is no value stored under that key).\n\nThe function lookup\nis defined in terms of the\nThe function assoc\nreturns the record that has the given key as its\nhead.\n\nThe function lookup\nthen checks to see that the resulting record returned by\nundefined,\nand returns the value (the\ntail)\nof the record.\n\n```javascript\nlookup1_example\n make_table1\n insert_into_table1\n\nconst t = make_table();\ninsert(\"a\", 10, t);\nlookup(\"a\", t);\n```\n\n```javascript\nlookup1\n lookup1_example\n 10\n\nfunction lookup(key, table) {\n const record = assoc(key, tail(table));\n return is_undefined(record)\n ? undefined\n : tail(record);\n}\nfunction assoc(key, records) {\n return is_null(records)\n ? undefined\n : equal(key, head(head(records)))\n ? head(records)\n : assoc(key, tail(records));\n}\n```", - "token_count": 275, + "content": "When we studied various ways of representing sets in chapter , we\nmentioned in section the task of\nmaintaining a table of records\nindexed by identifying keys.\n\nIn the\nimplementation of data-directed programming in\nsection , we made extensive use of\ntwo-dimensional tables, in which information is stored and retrieved\nusing two keys.\n\nHere we see how to build tables as mutable list\nstructures.\n\nWe first consider a\none-dimensional table, in which each value is\nstored under a single key.\n\nWe implement the table as a list of\nrecords, each of which is implemented as a pair consisting of a key\nand the associated value.\n\nThe records are glued together to form a\nlist by pairs whose\nheads\npoint to successive records.\n\nThese gluing pairs are called the\nbackbone of the table.\n\nIn order to have a place that we can\nchange when we add a new record to the table, we build the table as a\nheaded list.\n\nA headed list has a special backbone pair at the\nbeginning, which holds a dummy record in this case\nthe arbitrarily chosen\nstring \"*table*\".\n\nFigure\nshows the box-and-pointer diagram for the table\n\n```javascript\na: 1\nb: 2\nc: 3\n```\n\nA table represented as a headed list.\n\nTo extract information from a table we use the\nfunction,\nwhich takes a key as argument and returns the associated value (or\nundefined\nif\nthere is no value stored under that key).\n\nThe function lookup\nis defined in terms of the operation,\nwhich expects a key and a list of records as arguments.\n\nNote that\nnever sees the dummy record.\n\nThe function assoc\nreturns the record that has the given key as its\nhead.", + "token_count": 280, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3580,8 +4840,8 @@ "chunk_id": "Modularity_Objects_and_State_Representing_Tables_1" }, { - "content": "The function lookup\nthen checks to see that the resulting record returned by\nundefined,\nand returns the value (the\ntail)\nof the record.\n\nTo insert a value in a table under a specified key, we first use\npairing\nthe key with the value, and insert this at the head of the table s\nlist of records, after the dummy record.\n\nIf there already is a record with\nthis key, we set the\ntail\nof this record to the designated new value.\n\nThe header of the table\nprovides us with a fixed location to modify in order to insert the new\nrecord.\n\n```javascript\nlookup1\n insert_into_table1\n\nfunction insert(key, value, table) {\n const record = assoc(key, tail(table));\n if (is_undefined(record)) {\n set_tail(table,\n pair(pair(key, value), tail(table)));\n } else {\n set_tail(record, value);\n }\n return \"ok\";\n}\n```\n\nTo construct a new table, we simply create a list containing just the string\n\n```javascript\nmake_table1\n\nfunction make_table() {\n return list(\"*table*\");\n}\n```\n\nIn a two-dimensional table, each value is indexed by two keys.\n\nWe can\nconstruct such a table as a one-dimensional table in which each key\nidentifies a subtable.\n\nFigure\nshows the box-and-pointer diagram for the table\n\n```javascript\n\"math\":\n \"+\": 43\n \"-\": 45\n \"*\": 42\n\"letters\":\n \"a\": 97\n \"b\": 98\n```\n\nwhich has two subtables.\n\n(The subtables don t need a special header\nstring,\nsince the key that identifies the subtable serves this purpose.)\nA two-dimensional table.\n\nWhen we look up an item, we use the first key to identify the correct\nsubtable.\n\nThen we use the second key to identify the record within the\nsubtable.\n\n```javascript\nlookup2_example\n make_table2\n insert_into_table2\n\nconst t = list(\"*table*\");\ninsert(\"a\", \"b\", 10, t);\nlookup(\"a\", \"b\", t);\n```\n\n```javascript\njust_assoc\n\nfunction assoc(key, records) {\n return is_null(records)\n ? undefined\n : equal(key, head(head(records)))\n ? head(records)\n : assoc(key, tail(records));\n}\n```", - "token_count": 295, + "content": "The function assoc\nreturns the record that has the given key as its\nhead.\n\n```javascript\nlookup1_example\n make_table1\n insert_into_table1\n\nconst t = make_table();\ninsert(\"a\", 10, t);\nlookup(\"a\", t);\n```\n\n```javascript\nlookup1\n lookup1_example\n 10\n\nfunction lookup(key, table) {\n const record = assoc(key, tail(table));\n return is_undefined(record)\n ? undefined\n : tail(record);\n}\nfunction assoc(key, records) {\n return is_null(records)\n ? undefined\n : equal(key, head(head(records)))\n ? head(records)\n : assoc(key, tail(records));\n}\n```\n\nTo insert a value in a table under a specified key, we first use\nto see if there is already a record in\nthe table with this key.\n\nIf not, we form a new record by\npairing\nthe key with the value, and insert this at the head of the table s\nlist of records, after the dummy record.\n\nIf there already is a record with\nthis key, we set the\ntail\nof this record to the designated new value.\n\nThe header of the table\nprovides us with a fixed location to modify in order to insert the new\nrecord.\n\n```javascript\nlookup1\n insert_into_table1\n\nfunction insert(key, value, table) {\n const record = assoc(key, tail(table));\n if (is_undefined(record)) {\n set_tail(table,\n pair(pair(key, value), tail(table)));\n } else {\n set_tail(record, value);\n }\n return \"ok\";\n}\n```\n\nTo construct a new table, we simply create a list containing just the string :\n\n```javascript\nmake_table1\n\nfunction make_table() {\n return list(\"*table*\");\n}\n```\n\nIn a two-dimensional table, each value is indexed by two keys.\n\nWe can\nconstruct such a table as a one-dimensional table in which each key\nidentifies a subtable.\n\nFigure\nshows the box-and-pointer diagram for the table\n\n```javascript\n\"math\":\n \"+\": 43\n \"-\": 45\n \"*\": 42\n\"letters\":\n \"a\": 97\n \"b\": 98\n```\n\nwhich has two subtables.\n\n(The subtables don t need a special header\nstring,\nsince the key that identifies the subtable serves this purpose.)\nA two-dimensional table.", + "token_count": 296, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3590,8 +4850,8 @@ "chunk_id": "Modularity_Objects_and_State_Representing_Tables_2" }, { - "content": "Then we use the second key to identify the record within the\nsubtable.\n\n```javascript\nlookup2\n just_assoc\n lookup2_example\n 10\n\nfunction lookup(key_1, key_2, table) {\n const subtable = assoc(key_1, tail(table));\n if (is_undefined(subtable)) {\n return undefined;\n } else {\n const record = assoc(key_2, tail(subtable));\n return is_undefined(record)\n ? undefined\n : tail(record);\n }\n}\n```\n\nTo insert a new item under a pair of keys, we use (key_2,\n\n```javascript\njust_assoc\n insert_into_table2\n\nfunction insert(key_1, key_2, value, table) {\n const subtable = assoc(key_1, tail(table));\n if (is_undefined(subtable)) {\n set_tail(table,\n pair(list(key_1, pair(key_2, value)), tail(table)));\n } else {\n const record = assoc(key_2, tail(subtable));\n if (is_undefined(record)) {\n set_tail(subtable,\n pair(pair(key_2, value), tail(subtable)));\n } else {\n set_tail(record, value);\n }\n }\n return \"ok\";\n}\n```\n\nThe\ninsert\noperations defined above take the table as an argument.\n\nThis enables us to\nuse programs that access more than one table.\n\nAnother way to deal with\nmultiple tables is to have separate\ninsert\nfunctions\nfor each table.\n\nWe can do this by representing a table procedurally, as an\nobject that maintains an internal table as part of its local state.\n\nWhen\nsent an appropriate message, this table object supplies the\nfunction\nwith which to operate on the internal table.\n\nHere is a generator for\ntwo-dimensional tables represented in this fashion:", - "token_count": 203, + "content": "(The subtables don t need a special header\nstring,\nsince the key that identifies the subtable serves this purpose.)\nA two-dimensional table.\n\nThen we use the second key to identify the record within the\nsubtable.\n\n```javascript\nlookup2_example\n make_table2\n insert_into_table2\n\nconst t = list(\"*table*\");\ninsert(\"a\", \"b\", 10, t);\nlookup(\"a\", \"b\", t);\n```\n\n```javascript\njust_assoc\n\nfunction assoc(key, records) {\n return is_null(records)\n ? undefined\n : equal(key, head(head(records)))\n ? head(records)\n : assoc(key, tail(records));\n}\n```\n\n```javascript\nlookup2\n just_assoc\n lookup2_example\n 10\n\nfunction lookup(key_1, key_2, table) {\n const subtable = assoc(key_1, tail(table));\n if (is_undefined(subtable)) {\n return undefined;\n } else {\n const record = assoc(key_2, tail(subtable));\n return is_undefined(record)\n ? undefined\n : tail(record);\n }\n}\n```\n\nTo insert a new item under a pair of keys, we use\nto see if there is a subtable stored\nunder the first key.\n\nIf not, we build a new subtable containing the single\nrecord\n(key_2,\n) and insert it into the table under the\nfirst key.\n\nIf a subtable already exists for the first key, we insert the\nnew record into this subtable, using the insertion method for\none-dimensional tables described above:\n\n```javascript\njust_assoc\n insert_into_table2\n\nfunction insert(key_1, key_2, value, table) {\n const subtable = assoc(key_1, tail(table));\n if (is_undefined(subtable)) {\n set_tail(table,\n pair(list(key_1, pair(key_2, value)), tail(table)));\n } else {\n const record = assoc(key_2, tail(subtable));\n if (is_undefined(record)) {\n set_tail(subtable,\n pair(pair(key_2, value), tail(subtable)));\n } else {\n set_tail(record, value);\n }\n }\n return \"ok\";\n}\n```\n\nThe and\ninsert\noperations defined above take the table as an argument.\n\nThis enables us to\nuse programs that access more than one table.\n\nAnother way to deal with\nmultiple tables is to have separate and\ninsert\nfunctions\nfor each table.\n\nWe can do this by representing a table procedurally, as an\nobject that maintains an internal table as part of its local state.", + "token_count": 293, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3600,8 +4860,8 @@ "chunk_id": "Modularity_Objects_and_State_Representing_Tables_3" }, { - "content": "Here is a generator for\ntwo-dimensional tables represented in this fashion:\n\n```javascript\nmake_table2\n just_assoc\n\nfunction make_table() {\n const local_table = list(\"*table*\");\n function lookup(key_1, key_2) {\n const subtable = assoc(key_1, tail(local_table));\n if (is_undefined(subtable)) {\n return undefined;\n } else {\n const record = assoc(key_2, tail(subtable));\n return is_undefined(record)\n ? undefined\n : tail(record);\n }\n }\n function insert(key_1, key_2, value) {\n const subtable = assoc(key_1, tail(local_table));\n if (is_undefined(subtable)) {\n set_tail(local_table,\n pair(list(key_1, pair(key_2, value)),\n tail(local_table)));\n } else {\n const record = assoc(key_2, tail(subtable));\n if (is_undefined(record)) {\n set_tail(subtable,\n pair(pair(key_2, value), tail(subtable)));\n } else {\n set_tail(record, value);\n }\n }\n }\n function dispatch(m) {\n return m === \"lookup\"\n ? lookup\n : m === \"insert\"\n ? insert\n : error(m, \"unknown operation -- table\");\n }\n return dispatch;\n}\n```\n\nUsing make_table, we could for data-directed programming, as follows:\n\n```javascript\noperation_table_example\n\nput(\"a\", \"b\", 10);\nget(\"a\", \"b\");\n```\n\n```javascript\noperation_table\n make_table2\n operation_table_example\n 10\n\nconst operation_table = make_table();\nconst get = operation_table(\"lookup\");\nconst put = operation_table(\"insert\");\n```\n\nThe function get takes as arguments two keys, and make_table.", - "token_count": 163, + "content": "We can do this by representing a table procedurally, as an\nobject that maintains an internal table as part of its local state.\n\nHere is a generator for\ntwo-dimensional tables represented in this fashion:\n\n```javascript\nmake_table2\n just_assoc\n\nfunction make_table() {\n const local_table = list(\"*table*\");\n function lookup(key_1, key_2) {\n const subtable = assoc(key_1, tail(local_table));\n if (is_undefined(subtable)) {\n return undefined;\n } else {\n const record = assoc(key_2, tail(subtable));\n return is_undefined(record)\n ? undefined\n : tail(record);\n }\n }\n function insert(key_1, key_2, value) {\n const subtable = assoc(key_1, tail(local_table));\n if (is_undefined(subtable)) {\n set_tail(local_table,\n pair(list(key_1, pair(key_2, value)),\n tail(local_table)));\n } else {\n const record = assoc(key_2, tail(subtable));\n if (is_undefined(record)) {\n set_tail(subtable,\n pair(pair(key_2, value), tail(subtable)));\n } else {\n set_tail(record, value);\n }\n }\n }\n function dispatch(m) {\n return m === \"lookup\"\n ? lookup\n : m === \"insert\"\n ? insert\n : error(m, \"unknown operation -- table\");\n }\n return dispatch;\n}\n```\n\nUsing make_table, we could implement the and operations used in section for data-directed programming, as follows:\n\n```javascript\noperation_table_example\n\nput(\"a\", \"b\", 10);\nget(\"a\", \"b\");\n```\n\n```javascript\noperation_table\n make_table2\n operation_table_example\n 10\n\nconst operation_table = make_table();\nconst get = operation_table(\"lookup\");\nconst put = operation_table(\"insert\");\n```\n\nThe function get\ntakes as arguments two keys, and takes\nas arguments two keys and a value.\n\nBoth operations access the same\nlocal table, which is encapsulated within the object created by the\ncall to\nmake_table.", + "token_count": 219, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Modeling with Mutable Data", @@ -3620,8 +4880,8 @@ "chunk_id": "Modularity_Objects_and_State_Streams_1" }, { - "content": "In this section, we will see how to\nmodel change in terms of sequences that represent the time histories\nof the systems being modeled.\n\nTo accomplish this, we introduce new\ndata structures called streams.\n\nFrom an abstract point of view,\na stream is simply a sequence.\n\nHowever, we will find that the\nstraightforward implementation of streams as lists (as in\nsection ) doesn t fully reveal\nthe power of stream processing.\n\nAs an alternative, we introduce the\ntechnique of\ndelayed evaluation , which enables us to represent\nvery large (even infinite) sequences as streams.\n\nStream processing lets us model systems that have state without ever\nusing assignment or mutable data.\n\nThis has important implications,\nboth theoretical and practical, because we can build models that avoid\nthe drawbacks inherent in introducing assignment.\n\nOn the other hand,\nthe stream framework raises difficulties of its own, and the question\nof which modeling technique leads to more modular and more easily\nmaintained systems remains open.", - "token_count": 161, + "content": "In this section, we will see how to\nmodel change in terms of sequences that represent the time histories\nof the systems being modeled.\n\nFrom an abstract point of view,\na stream is simply a sequence.\n\nHowever, we will find that the\nstraightforward implementation of streams as lists (as in\nsection ) doesn t fully reveal\nthe power of stream processing.\n\nAs an alternative, we introduce the\ntechnique of\ndelayed evaluation , which enables us to represent\nvery large (even infinite) sequences as streams.\n\nStream processing lets us model systems that have state without ever\nusing assignment or mutable data.\n\nThis has important implications,\nboth theoretical and practical, because we can build models that avoid\nthe drawbacks inherent in introducing assignment.\n\nOn the other hand,\nthe stream framework raises difficulties of its own, and the question\nof which modeling technique leads to more modular and more easily\nmaintained systems remains open.", + "token_count": 151, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3630,8 +4890,8 @@ "chunk_id": "Modularity_Objects_and_State_Streams_2" }, { - "content": "Streams with delayed evaluation can be a powerful modeling tool,\nproviding many of the benefits of local state and assignment.\n\nMoreover, they avoid some of the theoretical tangles that accompany\nthe introduction of assignment into a programming language.\n\nThe stream approach can be illuminating because it allows us to build systems with different\n\nIn section , we introduced\niterative processes, which proceed by updating state variables.\n\nWe know now\nthat we can represent state as a timeless stream of values\nrather than as a set of variables to be updated.\n\nLet s adopt this\nperspective in revisiting the square-root\nfunction\nfrom section.\n\nRecall that the idea is to\ngenerate a sequence of better and better guesses for the square root of\n$x$ by applying over and over again the\nfunction\nthat improves guesses:\n\n```javascript\nsqrt_improve_example\n\nsqrt_improve(1.2, 2);\n```\n\n```javascript\nsqrt_improve\n average_definition\n sqrt_improve_example\n 1.4333333333333333\n\nfunction sqrt_improve(guess, x) {\n return average(guess, x / guess);\n}\n```\n\nIn our original\nfunction,\nwe made these guesses be the successive values of a state variable.\n\nInstead\nwe can generate the infinite stream of guesses, starting with an initial\nguess of 1:\n\n```javascript\nsqrt_stream\n sqrt_improve\n sqrt_stream_example\n\nfunction sqrt_stream(x) {\n return pair(1, () => stream_map(guess => sqrt_improve(guess, x),\n sqrt_stream(x)));\n}\n```\n\n```javascript\nsqrt_stream_example\n display_stream\n sqrt_stream\n 1.414213562373095\n\ndisplay_stream(sqrt_stream(2));\n\nstream_ref(sqrt_stream(2), 5);\n```\n\nWe can generate more and more terms of the stream to get better and\nbetter guesses.\n\nIf we like, we can write a\nfunction\nthat keeps generating terms until the answer is good enough.\n\n(See exercise.)\n\nAnother iteration that we can treat in the same way is to generate an\napproximation to\n$\\pi$ , based upon the\nalternating series that we saw in\nsection :\n\\[\n\\begin{array}{lll}\n\\dfrac {\\pi}{4} &=& 1-\\dfrac{1}{3}+\\dfrac{1}{5}-\\dfrac{1}{7}+\\cdots\n\\end{array}\n\\]\nWe first generate the stream of summands of the series (the reciprocals\nof the odd integers, with alternating signs).", - "token_count": 305, + "content": "Streams with delayed evaluation can be a powerful modeling tool,\nproviding many of the benefits of local state and assignment.\n\nMoreover, they avoid some of the theoretical tangles that accompany\nthe introduction of assignment into a programming language.\n\nThe stream approach can be illuminating because it allows us to build\nsystems with different\nmodule boundaries than systems organized around\nassignment to state variables.\n\nFor example, we can think of an entire\ntime series (or signal) as a focus of interest, rather than the values\nof the state variables at individual moments.\n\nThis makes it\nconvenient to combine and compare components of state from different\nmoments.\n\nIn section , we introduced\niterative processes, which proceed by updating state variables.\n\nWe know now\nthat we can represent state as a timeless stream of values\nrather than as a set of variables to be updated.\n\nLet s adopt this\nperspective in revisiting the square-root\nfunction\nfrom section.\n\nRecall that the idea is to\ngenerate a sequence of better and better guesses for the square root of\n$x$ by applying over and over again the\nfunction\nthat improves guesses:\n\n```javascript\nsqrt_improve_example\n\nsqrt_improve(1.2, 2);\n```\n\n```javascript\nsqrt_improve\n average_definition\n sqrt_improve_example\n 1.4333333333333333\n\nfunction sqrt_improve(guess, x) {\n return average(guess, x / guess);\n}\n```\n\nIn our original\nfunction,\nwe made these guesses be the successive values of a state variable.\n\nInstead\nwe can generate the infinite stream of guesses, starting with an initial\nguess of 1:\n\n```javascript\nsqrt_stream\n sqrt_improve\n sqrt_stream_example\n\nfunction sqrt_stream(x) {\n return pair(1, () => stream_map(guess => sqrt_improve(guess, x),\n sqrt_stream(x)));\n}\n```\n\n```javascript\nsqrt_stream_example\n display_stream\n sqrt_stream\n 1.414213562373095\n\ndisplay_stream(sqrt_stream(2));\n\nstream_ref(sqrt_stream(2), 5);\n\n1\n1.5\n1.4166666666666665\n1.4142156862745097\n1.4142135623746899\n...\n```\n\nWe can generate more and more terms of the stream to get better and\nbetter guesses.\n\nIf we like, we can write a\nfunction\nthat keeps generating terms until the answer is good enough.\n\n(See exercise.)", + "token_count": 307, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3640,8 +4900,8 @@ "chunk_id": "Modularity_Objects_and_State_Exploiting_the_Stream_Paradigm_1" }, { - "content": "Another iteration that we can treat in the same way is to generate an\napproximation to\n$\\pi$ , based upon the\nalternating series that we saw in\nsection :\n\\[\n\\begin{array}{lll}\n\\dfrac {\\pi}{4} &=& 1-\\dfrac{1}{3}+\\dfrac{1}{5}-\\dfrac{1}{7}+\\cdots\n\\end{array}\n\\]\nWe first generate the stream of summands of the series (the reciprocals\nof the odd integers, with alternating signs).\n\nThen we take the stream\nof sums of more and more terms (using the\npartial_sums function\nof exercise ) and scale the result by 4:\n\n```javascript\npi_stream\n display_stream\n scale_stream\n partial_sums\n pi_stream_example\n\nfunction pi_summands(n) {\n return pair(1 / n, () => stream_map(x => - x, pi_summands(n + 2)));\n}\nconst pi_stream = scale_stream(partial_sums(pi_summands(1)), 4);\n```\n\n```javascript\npi_stream_example\n pi_stream\n 3.017071817071818\n\ndisplay_stream(pi_stream);\n\nstream_ref(pi_stream, 7);\n```\n\nThis gives us a stream of better and better approximations to\n$\\pi$ , although the approximations converge\nrather slowly.\n\nEight terms of the sequence bound the value of\n$\\pi$ between 3.284 and 3.017.\n\nSo far, our use of the stream of states approach is not much different\nfrom updating state variables.\n\nBut streams give us an opportunity to do\nsome interesting tricks.\n\nFor example, we can transform a stream with a\nsequence accelerator that converts a sequence of approximations to a\nnew sequence that converges to the same value as the original, only faster.\n\nOne such accelerator, due to the eighteenth-century Swiss mathematician s technique, if $S_n$ is the $n$ th term of the original sum sequence, then the\n\naccelerated sequence has terms \\[ \\begin{array}{l} S_{n+1} - \\dfrac{(S_{n+1}-S_n)^2}{S_{n-1}-2S_n+S_{n+1}} \\end{array} \\] Thus, if the original sequence is represented as a stream of values, the transformed\n\nsequence is given by", - "token_count": 266, + "content": "(See exercise.)\n\nThen we take the stream\nof sums of more and more terms (using the\npartial_sums function\nof exercise ) and scale the result by 4:\n\n```javascript\npi_stream\n display_stream\n scale_stream\n partial_sums\n pi_stream_example\n\nfunction pi_summands(n) {\n return pair(1 / n, () => stream_map(x => - x, pi_summands(n + 2)));\n}\nconst pi_stream = scale_stream(partial_sums(pi_summands(1)), 4);\n```\n\n```javascript\npi_stream_example\n pi_stream\n 3.017071817071818\n\ndisplay_stream(pi_stream);\n\nstream_ref(pi_stream, 7);\n\n4\n2.666666666666667\n3.466666666666667\n2.8952380952380956\n3.3396825396825403\n2.9760461760461765\n3.2837384837384844\n3.017071817071818\n...\n```\n\nThis gives us a stream of better and better approximations to\n$\\pi$ , although the approximations converge\nrather slowly.\n\nEight terms of the sequence bound the value of\n$\\pi$ between 3.284 and 3.017.\n\nSo far, our use of the stream of states approach is not much different\nfrom updating state variables.\n\nBut streams give us an opportunity to do\nsome interesting tricks.\n\nFor example, we can transform a stream with a\nsequence accelerator that converts a sequence of approximations to a\nnew sequence that converges to the same value as the original, only faster.\n\nOne such accelerator, due to the eighteenth-century Swiss mathematician\nLeonhard Euler, works well with sequences that are partial sums of\nalternating series (series of terms with alternating signs).\n\nIn\nEuler s technique, if $S_n$ is the\n$n$ th term of the original sum sequence, then\nthe accelerated sequence has terms\n\\[\n\\begin{array}{l}\nS_{n+1} - \\dfrac{(S_{n+1}-S_n)^2}{S_{n-1}-2S_n+S_{n+1}}\n\\end{array}\n\\]\nThus, if the original sequence is represented as a stream of values,\nthe transformed sequence is given by\n\n```javascript\neuler_transform\n square_definition\n memo\n euler_transform_example\n 3.1418396189294033\n\nfunction euler_transform(s) {\n const s0 = stream_ref(s, 0); // $S_{n-1}$\n const s1 = stream_ref(s, 1); // $S_{n}$\n const s2 = stream_ref(s, 2); // $S_{n+1}$\n return pair(s2 - square(s2 - s1) / (s0 + (-2) * s1 + s2),\n memo(() => euler_transform(stream_tail(s))));\n}\n```", + "token_count": 291, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3650,8 +4910,8 @@ "chunk_id": "Modularity_Objects_and_State_Exploiting_the_Stream_Paradigm_2" }, { - "content": "sequence is given by\n\n```javascript\neuler_transform\n square_definition\n memo\n euler_transform_example\n 3.1418396189294033\n\nfunction euler_transform(s) {\n const s0 = stream_ref(s, 0); // $S_{n-1}$\n const s1 = stream_ref(s, 1); // $S_{n}$\n const s2 = stream_ref(s, 2); // $S_{n+1}$\n return pair(s2 - square(s2 - s1) / (s0 + (-2) * s1 + s2),\n memo(() => euler_transform(stream_tail(s))));\n}\n```\n\n```javascript\nNote that we make use of the memoization optimization of\n\t section, because in the\n\t following we will rely on repeated evaluation of the resulting stream.\n```\n\nWe can demonstrate Euler acceleration with our sequence of approximations to $\\pi$ :\n\n```javascript\neuler_transform_example\n euler_transform\n display_stream\n pi_stream\n 3.1418396189294033\n\ndisplay_stream(euler_transform(pi_stream));\n\nstream_ref(euler_transform(pi_stream), 8);\n```\n\nEven better, we can accelerate the accelerated sequence, and recursively\naccelerate that, and so on.\n\nNamely, we create a stream of streams (a\nstructure we ll call a\ntableau ) in which each stream is the transform of the preceding one:\n\n```javascript\nmake_tableau\n\nfunction make_tableau(transform, s) {\n return pair(s, () => make_tableau(transform, transform(s)));\n}\n```\n\nThe tableau has the form \\[ \\begin{array}{llllll} s_{00} & s_{01} & s_{02} & s_{03} & s_{04} & \\ldots\\\\ & s_{10} & s_{11} & s_{12} &\n\ns_{13} & \\ldots\\\\ & & s_{20} & s_{21} & s_{22} & \\ldots\\\\ & & & & \\ldots & \\end{array} \\] Finally, we form a sequence\n\nby taking the first term in each row of the tableau:\n\n```javascript\naccelerated_sequence\n make_tableau\n accelerated_sequence_example\n\nfunction accelerated_sequence(transform, s) {\n return stream_map(head, make_tableau(transform, s));\n}\n```\n\nWe can demonstrate this kind of super-acceleration of the $\\pi$ sequence:\n\n```javascript\naccelerated_sequence_example\n accelerated_sequence\n euler_transform\n pi_stream\n display_stream\n 3.1415926535911765\n\ndisplay_stream(accelerated_sequence(euler_transform, pi_stream));\n\nstream_ref(accelerated_sequence(euler_transform, pi_stream),\n 6);\n```\n\nThe result is impressive.\n\nTaking eight terms of the sequence yields the\ncorrect value of $\\pi$ to 14 decimal places.", - "token_count": 277, + "content": "In\nEuler s technique, if $S_n$ is the\n$n$ th term of the original sum sequence, then\nthe accelerated sequence has terms\n\\[\n\\begin{array}{l}\nS_{n+1} - \\dfrac{(S_{n+1}-S_n)^2}{S_{n-1}-2S_n+S_{n+1}}\n\\end{array}\n\\]\nThus, if the original sequence is represented as a stream of values,\nthe transformed sequence is given by\n\nWe can demonstrate Euler acceleration with our sequence of approximations to $\\pi$ :\n\n```javascript\neuler_transform_example\n euler_transform\n display_stream\n pi_stream\n 3.1418396189294033\n\ndisplay_stream(euler_transform(pi_stream));\n\nstream_ref(euler_transform(pi_stream), 8);\n\n3.166666666666667\n3.1333333333333337\n3.1452380952380956\n3.13968253968254\n3.1427128427128435\n3.1408813408813416\n3.142071817071818\n3.1412548236077655\n...\n```\n\nEven better, we can accelerate the accelerated sequence, and recursively\naccelerate that, and so on.\n\nNamely, we create a stream of streams (a\nstructure we ll call a\ntableau ) in which each stream is the transform of the preceding one:\n\n```javascript\nmake_tableau\n\nfunction make_tableau(transform, s) {\n return pair(s, () => make_tableau(transform, transform(s)));\n}\n```\n\nThe tableau has the form \\[ \\begin{array}{llllll} s_{00} & s_{01} & s_{02} & s_{03} & s_{04} & \\ldots\\\\ & s_{10} & s_{11} & s_{12} &\n\ns_{13} & \\ldots\\\\ & & s_{20} & s_{21} & s_{22} & \\ldots\\\\ & & & & \\ldots & \\end{array} \\] Finally, we form a sequence\n\nby taking the first term in each row of the tableau:\n\n```javascript\naccelerated_sequence\n make_tableau\n accelerated_sequence_example\n\nfunction accelerated_sequence(transform, s) {\n return stream_map(head, make_tableau(transform, s));\n}\n```\n\nWe can demonstrate this kind of super-acceleration of the $\\pi$ sequence:\n\n```javascript\naccelerated_sequence_example\n accelerated_sequence\n euler_transform\n pi_stream\n display_stream\n 3.1415926535911765\n\ndisplay_stream(accelerated_sequence(euler_transform, pi_stream));\n\nstream_ref(accelerated_sequence(euler_transform, pi_stream),\n 6);\n\n4\n3.166666666666667\n3.142105263157895\n3.141599357319005\n3.1415927140337785\n3.1415926539752927\n3.1415926535911765\n3.141592653589778\n...\n```\n\nThe result is impressive.\n\nTaking eight terms of the sequence yields the\ncorrect value of $\\pi$ to 14 decimal places.\n\nIf we had used only the original $\\pi$ sequence,\nwe would need to compute on the order of $10^{13}$\nterms (i.e., expanding the series far enough so that the individual terms\nare less then $10^{-13}$ ) to get that much\naccuracy!", + "token_count": 303, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3660,8 +4920,8 @@ "chunk_id": "Modularity_Objects_and_State_Exploiting_the_Stream_Paradigm_3" }, { - "content": "Taking eight terms of the sequence yields the\ncorrect value of $\\pi$ to 14 decimal places.\n\nIf we had used only the original $\\pi$ sequence,\nwe would need to compute on the order of $10^{13}$\nterms (i.e., expanding the series far enough so that the individual terms\nare less then $10^{-13}$ ) to get that much\naccuracy!\n\nWe could have implemented these acceleration techniques without using\nstreams.\n\nBut the stream formulation is particularly elegant and convenient\nbecause the entire sequence of states is available to us as a data structure\nthat can be manipulated with a uniform set of operations.\n\nIn section , we saw how the\nsequence paradigm handles traditional nested loops as processes defined\non sequences of pairs.\n\nIf we generalize this technique to infinite streams,\nthen we can write programs that are not easily represented as loops, because\nthe looping must range over an infinite set.\n\nFor example, suppose we want to generalize the\nprime_sum_pairs function\nof section to produce the stream\nof pairs of all integers $(i,j)$ with\n$i \\leq j$ such that\n$i+j$\nis prime.\n\nIf\nint_pairs\nis the sequence of all pairs of integers $(i,j)$\nwith $i \\leq j$ , then our required stream is\nsimply\n\n```javascript\nint_pairs\n pairs_second_attempt\n integers_definition\n display_stream\n\nconst int_pairs = pairs(integers, integers);\n```\n\n```javascript\nprime_sum_pairs_stream\n is_prime2\n int_pairs\n [ 1, [ 12, null ] ]\n\nstream_filter(pair => is_prime(head(pair) + head(tail(pair))),\n int_pairs);\n\nstream_ref(stream_filter(pair => is_prime(head(pair) + head(tail(pair))),\n int_pairs), 8);\n```\n\nOur problem, then, is to produce the stream\nint_pairs.", - "token_count": 247, + "content": "If we had used only the original $\\pi$ sequence,\nwe would need to compute on the order of $10^{13}$\nterms (i.e., expanding the series far enough so that the individual terms\nare less then $10^{-13}$ ) to get that much\naccuracy!\n\nBut the stream formulation is particularly elegant and convenient\nbecause the entire sequence of states is available to us as a data structure\nthat can be manipulated with a uniform set of operations.\n\nIn section , we saw how the\nsequence paradigm handles traditional nested loops as processes defined\non sequences of pairs.\n\nIf we generalize this technique to infinite streams,\nthen we can write programs that are not easily represented as loops, because\nthe looping must range over an infinite set.\n\nFor example, suppose we want to generalize the\nprime_sum_pairs function\nof section to produce the stream\nof pairs of all integers $(i,j)$ with\n$i \\leq j$ such that\n$i+j$\nis prime.\n\nIf\nint_pairs\nis the sequence of all pairs of integers $(i,j)$\nwith $i \\leq j$ , then our required stream is\nsimply\n\n```javascript\nint_pairs\n pairs_second_attempt\n integers_definition\n display_stream\n\nconst int_pairs = pairs(integers, integers);\n```\n\n```javascript\nprime_sum_pairs_stream\n is_prime2\n int_pairs\n [ 1, [ 12, null ] ]\n\nstream_filter(pair => is_prime(head(pair) + head(tail(pair))),\n int_pairs);\n\nstream_ref(stream_filter(pair => is_prime(head(pair) + head(tail(pair))),\n int_pairs), 8);\n```\n\nOur problem, then, is to produce the stream\nint_pairs.", + "token_count": 221, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3670,8 +4930,8 @@ "chunk_id": "Modularity_Objects_and_State_Exploiting_the_Stream_Paradigm_4" }, { - "content": "Our problem, then, is to produce the stream\nint_pairs.\n\nMore generally, suppose we have two streams\n$S = (S_i)$ and\n$T = (T_j)$ ,\nand imagine the infinite rectangular array\n\\[\n\\begin{array}{cccc}\n(S_0,T_0) & (S_0,T_1) & (S_0, T_2) & \\ldots\\\\\n(S_1,T_0) & (S_1,T_1) & (S_1, T_2) & \\ldots\\\\\n(S_2,T_0) & (S_2,T_1) & (S_2, T_2) & \\ldots\\\\\n\\ldots\n\\end{array}\n\\]\nWe wish to generate a stream that contains all the pairs in the array\nthat lie on or above the diagonal, i.e., the pairs\n\\[\n\\begin{array}{cccc}\n(S_0,T_0) & (S_0,T_1) & (S_0, T_2) & \\ldots\\\\\n& (S_1,T_1) & (S_1, T_2) & \\ldots\\\\\n& & (S_2, T_2) & \\ldots\\\\\n& & & \\ldots\n\\end{array}\n\\]\n(If we take both $S$ and\n$T$ to be the stream of integers, then this\nwill be our desired stream\nint_pairs.)\n\nCall the general stream of pairs\npairs(S, T),\nand consider it to be composed of three parts: the pair\n$(S_0,T_0)$ , the rest of the pairs in the first\nrow, and the remaining pairs:\n\\[\n\\begin{array}{c|ccc}\n(S_0,T_0) & (S_0,T_1) & (S_0, T_2) & \\ldots\\\\\n\\hline{} %--------------------------------------------------- \\\\\n& (S_1,T_1) & (S_1, T_2) & \\ldots\\\\\n& & (S_2, T_2) & \\ldots\\\\\n& & & \\ldots\n\\end{array}\n\\]\nObserve that the third piece in this decomposition (pairs that are not in\nthe first row) is (recursively) the pairs formed from\nstream_tail(S)\nand\nstream_tail(T).\n\nAlso note that the second piece (the rest of the first row) is\n\n```javascript\nstream_map(x => list(head(s), x),\n stream_tail(t));\n```\n\nThus we can form our stream of pairs as follows:\n\n```javascript\nfunction pairs(s, t) {\n return pair(list(head(s), head(t)),\n () => combine-in-some-way(\n stream_map(x => list(head(s), x),\n stream_tail(t)),\n pairs(stream_tail(s), stream_tail(t))));\n}\n```\n\nIn order to complete the function, we must choose some way to function from section :", - "token_count": 290, + "content": "Our problem, then, is to produce the stream\nint_pairs.\n\nCall the general stream of pairs\npairs(S, T),\nand consider it to be composed of three parts: the pair\n$(S_0,T_0)$ , the rest of the pairs in the first\nrow, and the remaining pairs:\n\\[\n\\begin{array}{c|ccc}\n(S_0,T_0) & (S_0,T_1) & (S_0, T_2) & \\ldots\\\\\n\\hline{} %--------------------------------------------------- \\\\\n& (S_1,T_1) & (S_1, T_2) & \\ldots\\\\\n& & (S_2, T_2) & \\ldots\\\\\n& & & \\ldots\n\\end{array}\n\\]\nObserve that the third piece in this decomposition (pairs that are not in\nthe first row) is (recursively) the pairs formed from\nstream_tail(S)\nand\nstream_tail(T).\n\nAlso note that the second piece (the rest of the first row) is\n\n```javascript\nstream_map(x => list(head(s), x),\n stream_tail(t));\n```\n\nThus we can form our stream of pairs as follows:\n\n```javascript\nfunction pairs(s, t) {\n return pair(list(head(s), head(t)),\n () => combine-in-some-way(\n stream_map(x => list(head(s), x),\n stream_tail(t)),\n pairs(stream_tail(s), stream_tail(t))));\n}\n```\n\nIn order to complete the\nfunction,\nwe must choose some way to\ncombine the two inner streams.\n\nOne idea is to\nuse the stream analog of the\nfunction\nfrom section :\n\n```javascript\nstream_append_example\n\nconst ones = pair(1, () => ones);\nconst twos = pair(2, () => twos);\nconst appended = stream_append(ones, twos);\nstream_ref(appended, 100);\n```\n\n```javascript\nstream_append\n stream_append_example\n 1\n\nfunction stream_append(s1, s2) {\n return is_null(s1)\n ? s2\n : pair(head(s1),\n () => stream_append(stream_tail(s1), s2));\n}\n```\n\nThis is unsuitable for infinite streams, however, because it takes all the\nelements from the first stream before incorporating the second stream.\n\nIn\nparticular, if we try to generate all pairs of positive integers using\n\n```javascript\npairs_first_attempt\n stream_append\n\nfunction pairs(s, t) {\n return pair(list(head(s), head(t)),\n () => stream_append(\n stream_map(x => list(head(s), x),\n stream_tail(t)),\n pairs(stream_tail(s), stream_tail(t)))\n );\n}\n```\n\n```javascript\npairs_first_attempt_usage\n pairs_first_attempt\n pairs_first_attempt_example\n [ 1, [ 9, null ] ]\n\npairs(integers, integers);\n```\n\n```javascript\npairs_first_attempt_example\n display_stream\n integers_definition\n\ndisplay_stream(pairs(integers, integers));\n\nstream_ref(pairs(integers, integers), 8);\n```", + "token_count": 307, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3680,8 +4940,8 @@ "chunk_id": "Modularity_Objects_and_State_Exploiting_the_Stream_Paradigm_5" }, { - "content": "In order to complete the function, we must choose some way to function from section :\n\n```javascript\nstream_append_example\n\nconst ones = pair(1, () => ones);\nconst twos = pair(2, () => twos);\nconst appended = stream_append(ones, twos);\nstream_ref(appended, 100);\n```\n\n```javascript\nstream_append\n stream_append_example\n 1\n\nfunction stream_append(s1, s2) {\n return is_null(s1)\n ? s2\n : pair(head(s1),\n () => stream_append(stream_tail(s1), s2));\n}\n```\n\nThis is unsuitable for infinite streams, however, because it takes all the\nelements from the first stream before incorporating the second stream.\n\nIn\nparticular, if we try to generate all pairs of positive integers using\n\n```javascript\npairs_first_attempt\n stream_append\n\nfunction pairs(s, t) {\n return pair(list(head(s), head(t)),\n () => stream_append(\n stream_map(x => list(head(s), x),\n stream_tail(t)),\n pairs(stream_tail(s), stream_tail(t)))\n );\n}\n```\n\n```javascript\npairs_first_attempt_usage\n pairs_first_attempt\n pairs_first_attempt_example\n [ 1, [ 9, null ] ]\n\npairs(integers, integers);\n```\n\n```javascript\npairs_first_attempt_example\n display_stream\n integers_definition\n\ndisplay_stream(pairs(integers, integers));\n\nstream_ref(pairs(integers, integers), 8);\n```\n\nour stream of results will first try to run through all pairs with the first integer equal to 1, and hence will never produce pairs\n\nwith any other value of the first integer.\n\nTo handle infinite streams, we need to devise an order of combination\nthat ensures that every element will eventually be reached if we let\nour program run long enough.\n\nAn elegant way to accomplish this is\nwith the following function :\n\n```javascript\ninterleave_example\n display_stream\n\nconst ones = pair(1, () => ones);\nconst twos = pair(2, () => twos);\nconst interleaved = interleave(ones, twos);\ndisplay_stream(interleaved);\n\nconst ones = pair(1, () => ones);\nconst twos = pair(2, () => twos);\nconst interleaved = interleave(ones, twos);\nstream_ref(interleaved, 7);\n```\n\n```javascript\ninterleave\n interleave_example\n 2\n\nfunction interleave(s1, s2) {\n return is_null(s1)\n ? s2\n : pair(head(s1),\n () => interleave(s2, stream_tail(s1)));\n}\n```\n\nSince\n\nWe can thus generate the required stream of pairs as", - "token_count": 292, + "content": "In\nparticular, if we try to generate all pairs of positive integers using\n\nwith any other value of the first integer.\n\nTo handle infinite streams, we need to devise an order of combination\nthat ensures that every element will eventually be reached if we let\nour program run long enough.\n\nAn elegant way to accomplish this is\nwith the following\nfunction :\n\n```javascript\ninterleave_example\n display_stream\n\nconst ones = pair(1, () => ones);\nconst twos = pair(2, () => twos);\nconst interleaved = interleave(ones, twos);\ndisplay_stream(interleaved);\n\nconst ones = pair(1, () => ones);\nconst twos = pair(2, () => twos);\nconst interleaved = interleave(ones, twos);\nstream_ref(interleaved, 7);\n```\n\n```javascript\ninterleave\n interleave_example\n 2\n\nfunction interleave(s1, s2) {\n return is_null(s1)\n ? s2\n : pair(head(s1),\n () => interleave(s2, stream_tail(s1)));\n}\n```\n\nSince takes elements alternately from the two streams, every element of the second stream will eventually find its way into the interleaved stream, even if\n\nthe first stream is infinite.\n\nWe can thus generate the required stream of pairs as\n\n```javascript\npairs_second_attempt\n interleave\n pairs_second_attempt_example\n [ 2, [ 4, null ] ]\n\nfunction pairs(s, t) {\n return pair(list(head(s), head(t)),\n () => interleave(stream_map(x => list(head(s), x),\n stream_tail(t)),\n pairs(stream_tail(s),\n stream_tail(t))));\n}\n```\n\n```javascript\npairs_second_attempt_example\n display_stream\n integers_definition\n\ndisplay_stream(pairs(integers, integers));\n\nstream_ref(pairs(integers, integers), 8);\n```\n\nWe began our discussion of streams by describing them as computational\nanalogs of the signals in signal-processing systems.\n\nIn fact, we can use streams to model signal-processing systems in a very\ndirect way, representing the values of a signal at successive time\nintervals as consecutive elements of a stream.\n\nFor instance, we can\nimplement an\nintegrator or\nsummer that, for an input stream\n$x=(x_{i})$ , an initial value $C$ , and a small increment $dt$ ,\naccumulates the sum\n\\[\n\\begin{array}{lll}\nS_i &=& C +\\sum_{j=1}^{i} x_{j} \\, dt\n\\end{array}\n\\]\nand returns the stream of values $S=(S_{i})$.", + "token_count": 303, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3690,8 +4950,8 @@ "chunk_id": "Modularity_Objects_and_State_Exploiting_the_Stream_Paradigm_6" }, { - "content": "We can thus generate the required stream of pairs as\n\n```javascript\npairs_second_attempt\n interleave\n pairs_second_attempt_example\n [ 2, [ 4, null ] ]\n\nfunction pairs(s, t) {\n return pair(list(head(s), head(t)),\n () => interleave(stream_map(x => list(head(s), x),\n stream_tail(t)),\n pairs(stream_tail(s),\n stream_tail(t))));\n}\n```\n\n```javascript\npairs_second_attempt_example\n display_stream\n integers_definition\n\ndisplay_stream(pairs(integers, integers));\n\nstream_ref(pairs(integers, integers), 8);\n```\n\nWe began our discussion of streams by describing them as computational\nanalogs of the signals in signal-processing systems.\n\nIn fact, we can use streams to model signal-processing systems in a very\ndirect way, representing the values of a signal at successive time\nintervals as consecutive elements of a stream.\n\nFor instance, we can\nimplement an\nintegrator or\nsummer that, for an input stream\n$x=(x_{i})$ , an initial value $C$ , and a small increment $dt$ ,\naccumulates the sum\n\\[\n\\begin{array}{lll}\nS_i &=& C +\\sum_{j=1}^{i} x_{j} \\, dt\n\\end{array}\n\\]\nand returns the stream of values $S=(S_{i})$.\n\nThe following\nfunction\nis reminiscent of the implicit style definition of the\nstream of integers (section ):\n\n```javascript\nintegral_1_example\n\nfunction numbers_starting_from(t, dt) {\n return pair(t,\n () => numbers_starting_from(t + dt, dt)\n );\n}\nconst dt = 0.01;\nconst linear = numbers_starting_from(0, dt);\nconst linear_integral = integral(linear, 0, dt);\n// computing integral from 0 to 3 of f(x) = x\n// (the integral is g(x) = 0.5 x^2, and therefore\n// the result is near 0.5 * 3^2 = 4.5)\nstream_ref(linear_integral, math_round(3 / dt));\n```\n\n```javascript\nintegral_1\n add_streams\n scale_stream\n integral_1_example\n 4.484999999999992\n\nfunction integral(integrand, initial_value, dt) {\n const integ = pair(initial_value,\n () => add_streams(scale_stream(integrand, dt),\n integ));\n return integ;\n}\n```\n\nThe\n\n\\noindent\nFigure\nis a picture of a signal-processing\nsystem that corresponds to the\nfunction.\n\nThe input stream is scaled by $dt$ and passed\nthrough an adder, whose output is passed back through the same adder.", - "token_count": 290, + "content": "For instance, we can\nimplement an\nintegrator or\nsummer that, for an input stream\n$x=(x_{i})$ , an initial value $C$ , and a small increment $dt$ ,\naccumulates the sum\n\\[\n\\begin{array}{lll}\nS_i &=& C +\\sum_{j=1}^{i} x_{j} \\, dt\n\\end{array}\n\\]\nand returns the stream of values $S=(S_{i})$.\n\n```javascript\nintegral_1_example\n\nfunction numbers_starting_from(t, dt) {\n return pair(t,\n () => numbers_starting_from(t + dt, dt)\n );\n}\nconst dt = 0.01;\nconst linear = numbers_starting_from(0, dt);\nconst linear_integral = integral(linear, 0, dt);\n// computing integral from 0 to 3 of f(x) = x\n// (the integral is g(x) = 0.5 x^2, and therefore\n// the result is near 0.5 * 3^2 = 4.5)\nstream_ref(linear_integral, math_round(3 / dt));\n```\n\n```javascript\nintegral_1\n add_streams\n scale_stream\n integral_1_example\n 4.484999999999992\n\nfunction integral(integrand, initial_value, dt) {\n const integ = pair(initial_value,\n () => add_streams(scale_stream(integrand, dt),\n integ));\n return integ;\n}\n```\n\nThe function viewed as a signal-processing system.\n\n\\noindent\nFigure\nis a picture of a signal-processing\nsystem that corresponds to the\nfunction.\n\nThe input stream is scaled by $dt$ and passed\nthrough an adder, whose output is passed back through the same adder.\n\nThe self-reference in the definition of\ninteg\nis reflected in the figure by the feedback loop that\nconnects the output of the adder to one of the inputs.", + "token_count": 209, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3700,18 +4960,8 @@ "chunk_id": "Modularity_Objects_and_State_Exploiting_the_Stream_Paradigm_7" }, { - "content": "The input stream is scaled by $dt$ and passed\nthrough an adder, whose output is passed back through the same adder.\n\nThe self-reference in the definition of\ninteg\nis reflected in the figure by the feedback loop that\nconnects the output of the adder to one of the inputs.", - "token_count": 49, - "has_code": false, - "chapter": "Modularity, Objects, and State", - "section": "Streams", - "subsection": "Exploiting the Stream Paradigm", - "chunk_index": 8, - "chunk_id": "Modularity_Objects_and_State_Exploiting_the_Stream_Paradigm_8" - }, - { - "content": "We have seen how to support the illusion of manipulating streams\nas complete entities even though, in actuality, we compute only\nas much of the stream as we need to access.\n\nWe can exploit this\ntechnique to represent sequences efficiently as streams, even if the\nsequences are very long.\n\nWhat is more striking, we can use streams to\nrepresent sequences that are infinitely long.\n\nFor instance, consider\nthe following definition of the stream of positive integers:\n\n```javascript\nintegers_starting_from_example\n\nconst from_20 = integers_starting_from(20);\neval_stream(from_20, 50);\n\nconst from_20 = integers_starting_from(20);\nstream_ref(from_20, 50);\n```\n\n```javascript\nintegers_starting_from\n integers_starting_from_example\n 70\n\nfunction integers_starting_from(n) {\n return pair(n, () => integers_starting_from(n + 1));\n}\n```\n\n```javascript\nintegers_definition\n integers_starting_from\n integers_definition_example\n 51\n\nconst integers = integers_starting_from(1);\n```\n\n```javascript\nintegers_definition_example\n\neval_stream(integers, 50);\n\nstream_ref(integers, 50);\n```\n\nThis makes sense because\nhead\nis 1 and whose\ntail\nis a promise to produce the integers beginning with 2.\n\nThis is an infinitely\nlong stream, but in any given time we can examine only a finite portion of\nit.\n\nThus, our programs will never know that the entire infinite stream is\nnot there.\n\nUsing\n\n```javascript\nis_divisible2_example\n\nis_divisible(42, 7);\n```\n\n```javascript\nis_divisible2\n is_divisible2_example\n\nfunction is_divisible(x, y) { return x % y === 0; }\n```\n\n```javascript\nno_sevens\n integers_definition\n is_divisible2\n no_sevens_example\n 27\n\nconst no_sevens = stream_filter(x => ! is_divisible(x, 7),\n integers);\n```\n\n```javascript\nno_sevens_example\n\neval_stream(no_sevens, 23);\n\nstream_ref(no_sevens, 23);\n```\n\nThen we can find integers not divisible by 7 simply by accessing elements of this stream:\n\n```javascript\non_sevens_example\n no_sevens\n 117\n\nstream_ref(no_sevens, 100);\n```\n\nIn analogy with\n\n```javascript\nfibgen_example\n\neval_stream(fibs, 50);\n\nstream_ref(fibs, 50);\n```\n\n```javascript\nfibgen\n fibgen_example\n 12586269025\n\nfunction fibgen(a, b) {\n return pair(a, () => fibgen(b, a + b));\n}\n\nconst fibs = fibgen(0, 1);\n```\n\nThe constant fibs\nis a pair whose\nhead\nis 0 and whose\ntail\nis a promise to evaluate\nfibgen(1, 1).", - "token_count": 298, + "content": "We have seen how to support the illusion of manipulating streams\nas complete entities even though, in actuality, we compute only\nas much of the stream as we need to access.\n\nWe can exploit this\ntechnique to represent sequences efficiently as streams, even if the\nsequences are very long.\n\nWhat is more striking, we can use streams to\nrepresent sequences that are infinitely long.\n\nFor instance, consider\nthe following definition of the stream of positive integers:\n\n```javascript\nintegers_starting_from_example\n\nconst from_20 = integers_starting_from(20);\neval_stream(from_20, 50);\n\nconst from_20 = integers_starting_from(20);\nstream_ref(from_20, 50);\n```\n\n```javascript\nintegers_starting_from\n integers_starting_from_example\n 70\n\nfunction integers_starting_from(n) {\n return pair(n, () => integers_starting_from(n + 1));\n}\n```\n\n```javascript\nintegers_definition\n integers_starting_from\n integers_definition_example\n 51\n\nconst integers = integers_starting_from(1);\n```\n\n```javascript\nintegers_definition_example\n\neval_stream(integers, 50);\n\nstream_ref(integers, 50);\n```\n\nThis makes sense because will be a\npair whose\nhead\nis 1 and whose\ntail\nis a promise to produce the integers beginning with 2.\n\nThis is an infinitely\nlong stream, but in any given time we can examine only a finite portion of\nit.\n\nThus, our programs will never know that the entire infinite stream is\nnot there.\n\nUsing we can define other infinite streams, such as the stream of integers that are not divisible by 7:\n\n```javascript\nis_divisible2_example\n\nis_divisible(42, 7);\n```\n\n```javascript\nis_divisible2\n is_divisible2_example\n\nfunction is_divisible(x, y) { return x % y === 0; }\n```\n\n```javascript\nno_sevens\n integers_definition\n is_divisible2\n no_sevens_example\n 27\n\nconst no_sevens = stream_filter(x => ! is_divisible(x, 7),\n integers);\n```\n\n```javascript\nno_sevens_example\n\neval_stream(no_sevens, 23);\n\nstream_ref(no_sevens, 23);\n```\n\nThen we can find integers not divisible by 7 simply by accessing elements of this stream:\n\n```javascript\non_sevens_example\n no_sevens\n 117\n\nstream_ref(no_sevens, 100);\n\n117\n```\n\nIn analogy with , we can define the infinite stream of Fibonacci numbers:\n\n```javascript\nfibgen_example\n\neval_stream(fibs, 50);\n\nstream_ref(fibs, 50);\n```\n\n```javascript\nfibgen\n fibgen_example\n 12586269025\n\nfunction fibgen(a, b) {\n return pair(a, () => fibgen(b, a + b));\n}\n\nconst fibs = fibgen(0, 1);\n```", + "token_count": 312, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3720,8 +4970,8 @@ "chunk_id": "Modularity_Objects_and_State_Infinite_Streams_1" }, { - "content": "The constant fibs\nis a pair whose\nhead\nis 0 and whose\ntail\nis a promise to evaluate\nfibgen(1, 1).\n\nWhen we evaluate this delayed\nfibgen(1, 1),\nit will produce a pair whose\nhead\nis 1 and whose\ntail\nis a promise to evaluate\nfibgen(1, 2),\nand so on.\n\nFor a look at a more exciting infinite stream, we can generalize the no_sevens example to construct the infinite stream of prime numbers, using a\n\nmethod known as the sieve of Eratosthenes.\n\n```javascript\nsieve_example\n\neval_stream(primes, 50);\n\nstream_ref(primes, 50);\n```\n\n```javascript\nsieve\n is_divisible2\n integers_starting_from\n sieve_example\n 233\n\nfunction sieve(stream) {\n return pair(head(stream),\n () => sieve(stream_filter(\n x => ! is_divisible(x, head(stream)),\n stream_tail(stream))));\n}\nconst primes = sieve(integers_starting_from(2));\n```\n\nNow to find a particular prime we need only ask for it:\n\n```javascript\nsieve_example_2\n sieve\n 233\n\nstream_ref(primes, 50);\n```\n\nIt is interesting to contemplate the signal-processing system set up\nby Henderson diagram in\nfigure.\nunpairer\nthat separates the first element of the stream from the rest of the stream.\n\nThe first element is used to construct a divisibility filter, through\nwhich the rest is passed, and the output of the filter is fed to\nanother sieve box.\n\nThen the original first element is\n\n```javascript\nadjoined to the output of the internal sieve\n\tto form the output stream.\n```\n\nThus, not only is the stream infinite, but the signal processor is also infinite, because the sieve contains a sieve within it.\n\n```javascript\nThe prime sieve viewed as a signal-processing system.\nEach solid line represents a\n stream of values being transmitted. The dashed line from the\n head\n to the\n pair\n and the\n```\n\nThe generating\nfunctions\nthat explicitly compute the stream elements one by one.\n\nAn alternative way\nto specify streams is to take advantage of delayed evaluation to define\nstreams implicitly.\n\nFor example, the following\nstatement\ndefines the\nstream\n\n```javascript\nones_example\n\neval_stream(ones, 50);\n\nstream_ref(ones, 50);\n```", - "token_count": 309, + "content": "In analogy with , we can define the infinite stream of Fibonacci numbers:\n\nWhen we evaluate this delayed\nfibgen(1, 1),\nit will produce a pair whose\nhead\nis 1 and whose\ntail\nis a promise to evaluate\nfibgen(1, 2),\nand so on.\n\nFor a look at a more exciting infinite stream, we can generalize the\nno_sevens\nexample to construct the infinite stream of prime\nnumbers, using a method known as the\nsieve of\nEratosthenes.\n\nWe start with the integers beginning with 2, which is the first prime.\n\nTo get the rest of the primes, we start by filtering the multiples of\n2 from the rest of the integers.\n\nThis leaves a stream beginning with\n3, which is the next prime.\n\nNow we filter the multiples of 3 from the\nrest of this stream.\n\nThis leaves a stream beginning with 5, which is\nthe next prime, and so on.\n\nIn other words, we construct the primes by\na sieving process, described as follows: To sieve a stream\nS,\nform a stream whose first element is the first element of\nS and\nthe rest of which is obtained by filtering all multiples of the\nfirst element of S out of the rest\nof S and sieving the result.\n\nThis\nprocess is readily described in terms of stream operations:\n\n```javascript\nsieve_example\n\neval_stream(primes, 50);\n\nstream_ref(primes, 50);\n```\n\n```javascript\nsieve\n is_divisible2\n integers_starting_from\n sieve_example\n 233\n\nfunction sieve(stream) {\n return pair(head(stream),\n () => sieve(stream_filter(\n x => ! is_divisible(x, head(stream)),\n stream_tail(stream))));\n}\nconst primes = sieve(integers_starting_from(2));\n```\n\nNow to find a particular prime we need only ask for it:\n\n```javascript\nsieve_example_2\n sieve\n 233\n\nstream_ref(primes, 50);\n\n233\n```\n\nIt is interesting to contemplate the signal-processing system set up\nby , shown in the\nHenderson diagram in\nfigure.", + "token_count": 288, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3730,8 +4980,8 @@ "chunk_id": "Modularity_Objects_and_State_Infinite_Streams_2" }, { - "content": "For example, the following\nstatement\ndefines the\nstream\n\n```javascript\nones_definition\n ones_example\n 1\n\nconst ones = pair(1, () => ones);\n```\n\nThis works much like the declaration of a recursive function: head is 1 and whose tail is a promise to evaluate tail gives us again\n\na 1 and a promise to evaluate\n\nWe can do more interesting things by manipulating streams with operations such as add_streams, which produces the elementwise sum of two given streams:\n\n```javascript\nadd_streams_example\n\nconst ones = pair(1, () => ones);\nconst twos = pair(2, () => twos);\nconst threes = add_streams(ones, twos);\neval_stream(threes, 50);\n\nconst ones = pair(1, () => ones);\nconst twos = pair(2, () => twos);\nconst threes = add_streams(ones, twos);\nstream_ref(threes, 50);\n```\n\n```javascript\nadd_streams\n stream_combine\n add_streams_example\n 3\n\nfunction add_streams(s1, s2) {\n return stream_map_2((x1, x2) => x1 + x2, s1, s2);\n}\n```\n\nNow we can define the integers as follows:\n\n```javascript\nintegers_definition_2_example\n\neval_stream(integers, 50);\n\nstream_ref(integers, 50);\n```\n\n```javascript\nintegers_definition_2\n add_streams\n ones_definition\n integers_definition_2_example\n 51\n\nconst integers = pair(1, () => add_streams(ones, integers));\n```\n\nThis defines\n\nWe can define the Fibonacci numbers in the same style:\n\n```javascript\nfibs_by_magic_example\n\neval_stream(fibs, 20);\n\nstream_ref(fibs, 20);\n```\n\n```javascript\nfibs_by_magic\n fibs_by_magic_example\n add_streams\n 6765\n\nconst fibs = pair(0,\n () => pair(1,\n () => add_streams(stream_tail(fibs),\n fibs)));\n```\n\nThis definition says that\n\n```javascript\n\\[\n\\begin{array}{ccccccccccccl}\n & & 1 & 1 & 2 & 3 & 5 & 8 & 13 & 21 & \\ldots & = & \\texttt{stream}\\mathtt{\\_}\\texttt{tail(fibs)} \\\\\n & & 0 & 1 & 1 & 2 & 3 & 5 & 8 & 13 & \\ldots & = & \\texttt{fibs} \\\\ \\hline\n0 & 1 & 1 & 2 & 3 & 5 & 8 & 13 & 21 & 34 & \\ldots & = & \\texttt{fibs}\n\\end{array}\n\\]\n```\n\n```javascript\nThe function\n\tscale_stream\n\tis also useful\n```\n\nin formulating such stream definitions.\n\nThis multiplies each item in a\nstream by a given constant:", - "token_count": 314, + "content": "It is interesting to contemplate the signal-processing system set up\nby , shown in the\nHenderson diagram in\nfigure.\n\nThe first element is used to construct a divisibility filter, through\nwhich the rest is passed, and the output of the filter is fed to\nanother sieve box.\n\nThen the original first element is\nadjoined to the output of the internal sieve to form the output stream.\n\nThus, not only is the stream infinite, but the signal processor is also\ninfinite, because the sieve contains a sieve within it.\n\nThe prime sieve viewed as a signal-processing system.\n\nEach solid line represents a stream of values being transmitted.\n\nThe dashed line from the head to the pair and the indicates that this is a single value rather than a stream.\n\nThe and\nstreams above were defined by specifying\ngenerating\nfunctions\nthat explicitly compute the stream elements one by one.\n\nAn alternative way\nto specify streams is to take advantage of delayed evaluation to define\nstreams implicitly.\n\nFor example, the following\nstatement\ndefines the\nstream to be an infinite stream of ones:\n\n```javascript\nones_example\n\neval_stream(ones, 50);\n\nstream_ref(ones, 50);\n```\n\n```javascript\nones_definition\n ones_example\n 1\n\nconst ones = pair(1, () => ones);\n```\n\nThis works much like the declaration of a recursive\nfunction:\nis a pair whose\nhead\nis 1 and whose\ntail\nis a promise to evaluate.\n\nEvaluating the\ntail\ngives us again a 1 and a promise to evaluate\n, and so on.\n\nWe can do more interesting things by manipulating streams with operations such as add_streams, which produces the elementwise sum of two given streams:", + "token_count": 263, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3740,8 +4990,8 @@ "chunk_id": "Modularity_Objects_and_State_Infinite_Streams_3" }, { - "content": "This multiplies each item in a\nstream by a given constant:\n\n```javascript\nscale_stream_example\n\nconst twos = pair(2, () => twos);\nconst sixes = scale_stream(twos, 3);\neval_stream(sixes, 50);\n\nconst twos = pair(2, () => twos);\nconst sixes = scale_stream(twos, 3);\nstream_ref(sixes, 50);\n```\n\n```javascript\nscale_stream\n scale_stream_example\n 6\n\nfunction scale_stream(stream, factor) {\n return stream_map(x => x * factor,\n stream);\n}\n```\n\nFor example,\n\n```javascript\ndouble_stream_example\n\neval_stream(double, 50);\n\nstream_ref(double, 50);\n```\n\n```javascript\ndouble_stream\n scale_stream\n double_stream_example\n 1125899906842624\n\nconst double = pair(1, () => scale_stream(double, 2));\n```\n\nproduces the stream of powers of 2: $1, 2, 4, 8, 16, 32,$.\n\nAn alternate definition of the stream of primes can be given by\nstarting with the integers and filtering them by testing for\nprimality.\n\nWe will need the first prime, 2, to get started:\n\n```javascript\nprimes_example\n\neval_stream(primes, 50);\n\nstream_ref(primes, 50);\n```\n\n```javascript\nprimes\n square_definition\n is_divisible2\n integers_starting_from\n primes_example\n 233\n\nfunction is_prime(n) {\n function iter(ps) {\n return square(head(ps)) > n\n ? true\n : is_divisible(n, head(ps))\n ? false\n : iter(stream_tail(ps));\n }\n return iter(primes);\n}\n\nconst primes = pair(2,\n () => stream_filter(is_prime,\n integers_starting_from(3)));\n\nconst primes = pair(2,\n () => stream_filter(is_prime,\n integers_starting_from(3)));\n```\n\nThis definition is not so straightforward as it appears, because we will test whether a number $n$ is prime by checking whether $n$ is divisible\n\nby a prime (not by just any integer) less than or equal to $\\sqrt{n}$ :\n\n```javascript\nis_prime2_example\n\nis_prime(100003);\n```\n\n```javascript\nis_prime2\n square_definition\n is_divisible2\n integers_starting_from\n is_prime2_example\n true\n\nfunction is_prime(n) {\n function iter(ps) {\n return square(head(ps)) > n\n ? true\n : is_divisible(n, head(ps))\n ? false\n : iter(stream_tail(ps));\n }\n return iter(primes);\n}\n\nfunction is_prime(n) {\n function iter(ps) {\n return square(head(ps)) > n\n ? true\n : is_divisible(n, head(ps))\n ? false\n : iter(stream_tail(ps));\n }\n return iter(primes);\n}\nconst primes = pair(2,\n () => stream_filter(\n is_prime,\n integers_starting_from(3))\n );\n```\n\nThis is a recursive definition, since is_prime predicate, which itself uses the function works is that, at any point, enough of the $n$ we test", - "token_count": 315, + "content": "We can do more interesting things by manipulating streams with operations such as add_streams, which produces the elementwise sum of two given streams:\n\n```javascript\nadd_streams\n stream_combine\n add_streams_example\n 3\n\nfunction add_streams(s1, s2) {\n return stream_map_2((x1, x2) => x1 + x2, s1, s2);\n}\n```\n\nNow we can define the integers as follows:\n\n```javascript\nintegers_definition_2_example\n\neval_stream(integers, 50);\n\nstream_ref(integers, 50);\n```\n\n```javascript\nintegers_definition_2\n add_streams\n ones_definition\n integers_definition_2_example\n 51\n\nconst integers = pair(1, () => add_streams(ones, integers));\n```\n\nThis defines to be a stream whose\nfirst element is 1 and the rest of which is the sum of\nand.\n\nThus, the second element of is 1 plus\nthe first element of , or 2; the third\nelement of is 1 plus the second\nelement of , or 3; and so on.\n\nThis\ndefinition works because, at any point, enough of the\nstream has been generated so that we\ncan feed it back into the definition to produce the next integer.\n\nWe can define the Fibonacci numbers in the same style:\n\n```javascript\nfibs_by_magic_example\n\neval_stream(fibs, 20);\n\nstream_ref(fibs, 20);\n```\n\n```javascript\nfibs_by_magic\n fibs_by_magic_example\n add_streams\n 6765\n\nconst fibs = pair(0,\n () => pair(1,\n () => add_streams(stream_tail(fibs),\n fibs)));\n```\n\nThis definition says that is a stream beginning with 0 and 1, such that the rest of the stream can be generated by adding to\n\nitself shifted by one place: \\[ \\begin{array}{ccccccccccccl} & & 1 & 1 & 2 & 3 & 5 & 8 & 13 & 21 &\n\n\\ldots & = & \\texttt{stream}\\mathtt{\\_}\\texttt{tail(fibs)} \\\\ & & 0 & 1 & 1 & 2 & 3 & 5 & 8 & 13 & \\ldots\n\n& = & \\texttt{fibs} \\\\ \\hline 0 & 1 & 1 & 2 & 3 & 5 & 8 & 13 & 21 & 34\n\n& \\ldots & = & \\texttt{fibs} \\end{array} \\]\n\nThe function scale_stream is also useful\nin formulating such stream definitions.", + "token_count": 308, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3750,9 +5000,9 @@ "chunk_id": "Modularity_Objects_and_State_Infinite_Streams_4" }, { - "content": "This is a recursive definition, since is_prime predicate, which itself uses the function works is that, at any point, enough of the $n$ we test\n\nfor primality, either $n$ is not prime (in which case there is a prime already generated that divides it) or $n$ is prime (in which\n\ncase there is a prime already generated i.e., a prime less than $n$ that is greater than $\\sqrt{n}$ ).", - "token_count": 69, - "has_code": false, + "content": "The function scale_stream is also useful\nin formulating such stream definitions.\n\n```javascript\nscale_stream_example\n\nconst twos = pair(2, () => twos);\nconst sixes = scale_stream(twos, 3);\neval_stream(sixes, 50);\n\nconst twos = pair(2, () => twos);\nconst sixes = scale_stream(twos, 3);\nstream_ref(sixes, 50);\n```\n\n```javascript\nscale_stream\n scale_stream_example\n 6\n\nfunction scale_stream(stream, factor) {\n return stream_map(x => x * factor,\n stream);\n}\n```\n\nFor example,\n\n```javascript\ndouble_stream_example\n\neval_stream(double, 50);\n\nstream_ref(double, 50);\n```\n\n```javascript\ndouble_stream\n scale_stream\n double_stream_example\n 1125899906842624\n\nconst double = pair(1, () => scale_stream(double, 2));\n```\n\nproduces the stream of powers of 2: $1, 2, 4, 8, 16, 32,$.\n\nAn alternate definition of the stream of primes can be given by\nstarting with the integers and filtering them by testing for\nprimality.\n\nWe will need the first prime, 2, to get started:\n\n```javascript\nprimes_example\n\neval_stream(primes, 50);\n\nstream_ref(primes, 50);\n```\n\n```javascript\nprimes\n square_definition\n is_divisible2\n integers_starting_from\n primes_example\n 233\n\nfunction is_prime(n) {\n function iter(ps) {\n return square(head(ps)) > n\n ? true\n : is_divisible(n, head(ps))\n ? false\n : iter(stream_tail(ps));\n }\n return iter(primes);\n}\n\nconst primes = pair(2,\n () => stream_filter(is_prime,\n integers_starting_from(3)));\n\nconst primes = pair(2,\n () => stream_filter(is_prime,\n integers_starting_from(3)));\n```\n\nThis definition is not so straightforward as it appears, because we will test whether a number $n$ is prime by checking whether $n$ is divisible\n\nby a prime (not by just any integer) less than or equal to $\\sqrt{n}$ :\n\n```javascript\nis_prime2_example\n\nis_prime(100003);\n```\n\n```javascript\nis_prime2\n square_definition\n is_divisible2\n integers_starting_from\n is_prime2_example\n true\n\nfunction is_prime(n) {\n function iter(ps) {\n return square(head(ps)) > n\n ? true\n : is_divisible(n, head(ps))\n ? false\n : iter(stream_tail(ps));\n }\n return iter(primes);\n}\n\nfunction is_prime(n) {\n function iter(ps) {\n return square(head(ps)) > n\n ? true\n : is_divisible(n, head(ps))\n ? false\n : iter(stream_tail(ps));\n }\n return iter(primes);\n}\nconst primes = pair(2,\n () => stream_filter(\n is_prime,\n integers_starting_from(3))\n );\n```\n\nThis is a recursive definition, since\nis defined in terms of the\nis_prime\npredicate, which itself uses the stream.", + "token_count": 309, + "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", "subsection": "Infinite Streams", @@ -3760,8 +5010,18 @@ "chunk_id": "Modularity_Objects_and_State_Infinite_Streams_5" }, { - "content": "As we saw in section , one of\nthe major benefits of introducing assignment is that we can increase the\nmodularity of our systems by encapsulating, or hiding, parts\nof the state of a large system within local variables.\n\nStream models can\nprovide an equivalent modularity without the use of assignment.\n\nAs an\nillustration, we can reimplement the\n$\\pi$ , which we examined in\nsection , from a\nstream-processing point of view.\n\nThe key modularity issue was that we wished to hide the internal state\nof a random-number generator from programs that used random numbers.\n\nWe began with a\nfunction rand_update,\nwhose successive values furnished our supply of random numbers, and used\nthis to produce a random-number generator:\n\n```javascript\nrand_update\n random_init\n rand_example\n 40083849805\n\nfunction make_rand() {\n let x = random_init;\n return () => {\n x = rand_update(x);\n return x;\n };\n}\nconst rand = make_rand();\n```\n\nIn the stream formulation there is no random-number generator per se , just a stream of random numbers produced by successive calls to rand_update:\n\n```javascript\nrandom_numbers\n rand_update\n random_init\n random_numbers_example\n 172561279022\n\nconst random_numbers =\n pair(random_init,\n () => stream_map(rand_update, random_numbers));\n```\n\n```javascript\nrandom_numbers_example\n\neval_stream(random_numbers, 5);\n\nstream_ref(random_numbers, 4);\n```\n\nWe use this to construct the stream of outcomes of the Ces ro experiment performed on consecutive pairs in the stream:\n\n```javascript\ncesaro_stream\n random_numbers\n gcd_definition\n cesaro_stream_example\n true\n\nfunction map_successive_pairs(f, s) {\n return pair(f(head(s), head(stream_tail(s))),\n () => map_successive_pairs(\n f,\n stream_tail(stream_tail(s))));\n}\nconst dirichlet_stream =\n map_successive_pairs((r1, r2) => gcd(r1, r2) === 1,\n random_numbers);\n```\n\n```javascript\ncesaro_stream_example\n\neval_stream(dirichlet_stream, 20);\n\nstream_ref(dirichlet_stream, 42);\n```\n\nThe\ndirichlet_stream\nis now fed to a\nmonte_carlo\nfunction,\nwhich produces a stream of estimates of probabilities.\n\nThe results are then\nconverted into a stream of estimates of $\\pi$.\n\nThis version of the program doesn t need a parameter telling how many\ntrials to perform.", - "token_count": 296, + "content": "This is a recursive definition, since\nis defined in terms of the\nis_prime\npredicate, which itself uses the stream.\n\nThat is, for every\n$n$ we test for primality, either\n$n$ is not prime (in which case there is a prime\nalready generated that divides it) or $n$ is\nprime (in which case there is a prime already generated i.e., a\nprime less than $n$ that is greater than\n$\\sqrt{n}$ ).", + "token_count": 70, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "Streams", + "subsection": "Infinite Streams", + "chunk_index": 6, + "chunk_id": "Modularity_Objects_and_State_Infinite_Streams_6" + }, + { + "content": "As we saw in section , one of\nthe major benefits of introducing assignment is that we can increase the\nmodularity of our systems by encapsulating, or hiding, parts\nof the state of a large system within local variables.\n\nStream models can\nprovide an equivalent modularity without the use of assignment.\n\nAs an\nillustration, we can reimplement the\nMonte Carlo estimation\nof $\\pi$ , which we examined in\nsection , from a\nstream-processing point of view.\n\nThe key modularity issue was that we wished to hide the internal state\nof a random-number generator from programs that used random numbers.\n\nWe began with a\nfunction rand_update,\nwhose successive values furnished our supply of random numbers, and used\nthis to produce a random-number generator:\n\n```javascript\nrand_update\n random_init\n rand_example\n 40083849805\n\nfunction make_rand() {\n let x = random_init;\n return () => {\n x = rand_update(x);\n return x;\n };\n}\nconst rand = make_rand();\n```\n\nIn the stream formulation there is no random-number generator per se , just a stream of random numbers produced by successive calls to rand_update:\n\n```javascript\nrandom_numbers\n rand_update\n random_init\n random_numbers_example\n 172561279022\n\nconst random_numbers =\n pair(random_init,\n () => stream_map(rand_update, random_numbers));\n```\n\n```javascript\nrandom_numbers_example\n\neval_stream(random_numbers, 5);\n\nstream_ref(random_numbers, 4);\n```\n\nWe use this to construct the stream of outcomes of the Ces ro experiment performed on consecutive pairs in the stream:\n\n```javascript\ncesaro_stream\n random_numbers\n gcd_definition\n cesaro_stream_example\n true\n\nfunction map_successive_pairs(f, s) {\n return pair(f(head(s), head(stream_tail(s))),\n () => map_successive_pairs(\n f,\n stream_tail(stream_tail(s))));\n}\nconst dirichlet_stream =\n map_successive_pairs((r1, r2) => gcd(r1, r2) === 1,\n random_numbers);\n```\n\n```javascript\ncesaro_stream_example\n\neval_stream(dirichlet_stream, 20);\n\nstream_ref(dirichlet_stream, 42);\n```\n\nThe\ndirichlet_stream\nis now fed to a\nmonte_carlo\nfunction,\nwhich produces a stream of estimates of probabilities.\n\nThe results are then\nconverted into a stream of estimates of $\\pi$.\n\nThis version of the program doesn t need a parameter telling how many\ntrials to perform.", + "token_count": 300, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3770,8 +5030,8 @@ "chunk_id": "Modularity_Objects_and_State_Modularity_of_Functional_Programs_and_Modularity_of_Objects_1" }, { - "content": "This version of the program doesn t need a parameter telling how many\ntrials to perform.\n\nBetter estimates of $\\pi$\n(from performing more experiments) are obtained by looking farther into the\n\n```javascript\nmonte_carlo_stream\n cesaro_stream\n display_pi\n 3.1780497164141406\n\nfunction monte_carlo(experiment_stream, passed, failed) {\n function next(passed, failed) {\n return pair(passed / (passed + failed),\n () => monte_carlo(stream_tail(experiment_stream),\n passed, failed));\n }\n return head(experiment_stream)\n ? next(passed + 1, failed)\n : next(passed, failed + 1);\n}\nconst pi = stream_map(p => math_sqrt(6 / p),\n monte_carlo(dirichlet_stream, 0, 0));\n```\n\n```javascript\ndisplay_pi\n\nstream_ref(pi, 100);\n```\n\nThere is considerable\nmonte_carlo function\nthat can deal with arbitrary experiments.\n\nYet there is no assignment or\nlocal state.\n\nLet us now return to the issues of objects and state that were raised\nat the beginning of this chapter and examine them in a new light.\n\nWe\nintroduced assignment and mutable objects to provide a mechanism for\nmodular construction of programs that model systems with state.\n\nWe constructed computational objects with local state variables and used\nassignment to modify these variables.\n\nWe modeled the temporal behavior of\nthe objects in the world by the temporal behavior of the corresponding\ncomputational objects.\n\nNow we have seen that streams provide an alternative way to model\nobjects with local state.\n\nWe can model a changing quantity, such as\nthe local state of some object, using a stream that represents the\ntime history of successive states.\n\nIn essence, we represent time\nexplicitly, using streams, so that we decouple time in our simulated\nworld from the sequence of events that take place during evaluation.\n\nIndeed, because of the presence of\ndelayed evaluation\nthere may be little relation between simulated time in the model and the\norder of events during the evaluation.", - "token_count": 283, + "content": "This version of the program doesn t need a parameter telling how many\ntrials to perform.\n\n```javascript\nmonte_carlo_stream\n cesaro_stream\n display_pi\n 3.1780497164141406\n\nfunction monte_carlo(experiment_stream, passed, failed) {\n function next(passed, failed) {\n return pair(passed / (passed + failed),\n () => monte_carlo(stream_tail(experiment_stream),\n passed, failed));\n }\n return head(experiment_stream)\n ? next(passed + 1, failed)\n : next(passed, failed + 1);\n}\nconst pi = stream_map(p => math_sqrt(6 / p),\n monte_carlo(dirichlet_stream, 0, 0));\n```\n\n```javascript\ndisplay_pi\n\nstream_ref(pi, 100);\n```\n\nThere is considerable\nmodularity in this approach, because we still\ncan formulate a general\nmonte_carlo function\nthat can deal with arbitrary experiments.\n\nYet there is no assignment or\nlocal state.\n\nLet us now return to the issues of objects and state that were raised\nat the beginning of this chapter and examine them in a new light.\n\nWe\nintroduced assignment and mutable objects to provide a mechanism for\nmodular construction of programs that model systems with state.\n\nWe constructed computational objects with local state variables and used\nassignment to modify these variables.\n\nWe modeled the temporal behavior of\nthe objects in the world by the temporal behavior of the corresponding\ncomputational objects.\n\nNow we have seen that streams provide an alternative way to model\nobjects with local state.\n\nWe can model a changing quantity, such as\nthe local state of some object, using a stream that represents the\ntime history of successive states.\n\nIn essence, we represent time\nexplicitly, using streams, so that we decouple time in our simulated\nworld from the sequence of events that take place during evaluation.\n\nIndeed, because of the presence of\ndelayed evaluation\nthere may be little relation between simulated time in the model and the\norder of events during the evaluation.", + "token_count": 279, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3780,8 +5040,8 @@ "chunk_id": "Modularity_Objects_and_State_Modularity_of_Functional_Programs_and_Modularity_of_Objects_2" }, { - "content": "Indeed, because of the presence of\ndelayed evaluation\nthere may be little relation between simulated time in the model and the\norder of events during the evaluation.\n\nIn order to contrast these two approaches to modeling, let us reconsider the implementation of a withdrawal processor that monitors the balance in a we\n\nimplemented a simplified version of such a processor:\n\n```javascript\nmake_simplified_withdraw_example\n make_simplified_withdraw_example1\n make_simplified_withdraw_example2\n\nfunction make_simplified_withdraw(balance) {\n return amount => {\n balance = balance - amount;\n return balance;\n };\n}\n```\n\nCalls to produce computational objects, each with a local state variable\n\nAlternatively, we can model a withdrawal processor as a function that takes as input a balance and a stream of amounts to withdraw and produces\n\nthe stream of successive balances in the account:\n\n```javascript\nstream_withdraw\n stream_withdraw_example\n 50\n\nfunction stream_withdraw(balance, amount_stream) {\n return pair(balance,\n () => stream_withdraw(balance - head(amount_stream),\n stream_tail(amount_stream)));\n}\n```\n\n```javascript\nstream_withdraw_example\n\nconst my_amounts = list_to_stream(list(50, 100, 40));\nconst my_account_stream = stream_withdraw(200, my_amounts);\neval_stream(my_account_stream, 3);\n\nconst my_amounts = list_to_stream(list(50, 100, 40));\nconst my_account_stream = stream_withdraw(200, my_amounts);\nstream_ref(my_account_stream, 2);\n```\n\nThe function stream_withdraw\nimplements a well-defined mathematical function whose output is fully\ndetermined by its input.\n\nSuppose, however, that the input\namount_stream\nis the stream of successive values typed by the user and that the resulting\nstream of balances is displayed.\n\nThen, from the perspective of the user who\nis typing values and watching results, the stream process has the same\nbehavior as the object created by\nmake_simplified_withdraw.\n\nHowever, with the stream version, there is no assignment, no local state\nvariable, and consequently none of the theoretical difficulties that we\nencountered.\n\nYet the system\nhas state!\n\nThis is really remarkable.\n\nEven though\nstream_withdraw\nimplements a well-defined mathematical function whose behavior does not\nchange, the user s perception here is one of interacting with a system\nthat has a changing state.", - "token_count": 302, + "content": "Indeed, because of the presence of\ndelayed evaluation\nthere may be little relation between simulated time in the model and the\norder of events during the evaluation.\n\nIn\nsection we implemented a\nsimplified version of such a processor:\n\n```javascript\nmake_simplified_withdraw_example\n make_simplified_withdraw_example1\n make_simplified_withdraw_example2\n\nfunction make_simplified_withdraw(balance) {\n return amount => {\n balance = balance - amount;\n return balance;\n };\n}\n```\n\nCalls to\nproduce computational objects, each with a local state variable\nthat is decremented by successive calls\nto the object.\n\nThe object takes an as\nan argument and returns the new balance.\n\nWe can imagine the user of a bank\naccount typing a sequence of inputs to such an object and observing the\nsequence of returned values shown on a display screen.\n\nAlternatively, we can model a withdrawal processor as a function that takes as input a balance and a stream of amounts to withdraw and produces\n\nthe stream of successive balances in the account:\n\n```javascript\nstream_withdraw\n stream_withdraw_example\n 50\n\nfunction stream_withdraw(balance, amount_stream) {\n return pair(balance,\n () => stream_withdraw(balance - head(amount_stream),\n stream_tail(amount_stream)));\n}\n```\n\n```javascript\nstream_withdraw_example\n\nconst my_amounts = list_to_stream(list(50, 100, 40));\nconst my_account_stream = stream_withdraw(200, my_amounts);\neval_stream(my_account_stream, 3);\n\nconst my_amounts = list_to_stream(list(50, 100, 40));\nconst my_account_stream = stream_withdraw(200, my_amounts);\nstream_ref(my_account_stream, 2);\n```\n\nThe function stream_withdraw\nimplements a well-defined mathematical function whose output is fully\ndetermined by its input.\n\nSuppose, however, that the input\namount_stream\nis the stream of successive values typed by the user and that the resulting\nstream of balances is displayed.\n\nThen, from the perspective of the user who\nis typing values and watching results, the stream process has the same\nbehavior as the object created by\nmake_simplified_withdraw.\n\nHowever, with the stream version, there is no assignment, no local state\nvariable, and consequently none of the theoretical difficulties that we\nencountered\nin section.\n\nYet the system\nhas state!\n\nThis is really remarkable.", + "token_count": 303, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3790,8 +5050,8 @@ "chunk_id": "Modularity_Objects_and_State_Modularity_of_Functional_Programs_and_Modularity_of_Objects_3" }, { - "content": "Even though\nstream_withdraw\nimplements a well-defined mathematical function whose behavior does not\nchange, the user s perception here is one of interacting with a system\nthat has a changing state.\n\nOne way to resolve this paradox is to realize\nthat it is the user s temporal existence that imposes state on the\nsystem.\n\nIf the user could step back from the interaction and think in terms\nof streams of balances rather than individual transactions, the system\nwould appear stateless.\n\nFrom the point of view of one part of a complex process, the other parts\nappear to change with time.\n\nThey have hidden time-varying local state.\n\nIf\nwe wish to write programs that model this kind of natural decomposition in\nour world (as we see it from our viewpoint as a part of that world) with\nstructures in our computer, we make computational objects that are not\nfunctional they must change with time.\n\nWe model state with local\nstate variables, and we model the changes of state with assignments to\nthose variables.\n\nBy doing this we make the time of execution of a\ncomputation model time in the world that we are part of, and thus we\nget objects in our computer.\n\nModeling with objects is powerful and intuitive, largely because this\nmatches the perception of interacting with a world of which we are\npart.\n\nHowever, as we ve seen repeatedly throughout this chapter,\nthese models raise thorny problems of constraining the order of events\nand of synchronizing multiple processes.\n\nThe possibility of avoiding\nthese problems has stimulated the development of\nfunctional programming languages , which do not include any\nprovision for assignment or mutable data.\n\nIn such a language, all\nfunctions\nimplement well-defined mathematical functions of their arguments,\nwhose behavior does not change.", - "token_count": 293, + "content": "This is really remarkable.\n\nOne way to resolve this paradox is to realize\nthat it is the user s temporal existence that imposes state on the\nsystem.\n\nIf the user could step back from the interaction and think in terms\nof streams of balances rather than individual transactions, the system\nwould appear stateless.\n\nFrom the point of view of one part of a complex process, the other parts\nappear to change with time.\n\nThey have hidden time-varying local state.\n\nIf\nwe wish to write programs that model this kind of natural decomposition in\nour world (as we see it from our viewpoint as a part of that world) with\nstructures in our computer, we make computational objects that are not\nfunctional they must change with time.\n\nWe model state with local\nstate variables, and we model the changes of state with assignments to\nthose variables.\n\nBy doing this we make the time of execution of a\ncomputation model time in the world that we are part of, and thus we\nget objects in our computer.\n\nModeling with objects is powerful and intuitive, largely because this\nmatches the perception of interacting with a world of which we are\npart.\n\nHowever, as we ve seen repeatedly throughout this chapter,\nthese models raise thorny problems of constraining the order of events\nand of synchronizing multiple processes.\n\nThe possibility of avoiding\nthese problems has stimulated the development of\nfunctional programming languages , which do not include any\nprovision for assignment or mutable data.\n\nIn such a language, all\nfunctions\nimplement well-defined mathematical functions of their arguments,\nwhose behavior does not change.\n\nThe functional approach is extremely\nattractive for dealing with\nconcurrent systems.\n\nOn the other hand, if we look closely, we can see time-related problems\ncreeping into functional models as well.", + "token_count": 297, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3800,8 +5060,8 @@ "chunk_id": "Modularity_Objects_and_State_Modularity_of_Functional_Programs_and_Modularity_of_Objects_4" }, { - "content": "In such a language, all\nfunctions\nimplement well-defined mathematical functions of their arguments,\nwhose behavior does not change.\n\nThe functional approach is extremely\nattractive for dealing with\n\nOn the other hand, if we look closely, we can see time-related problems\ncreeping into functional models as well.\n\nOne particularly troublesome area\narises when we wish to design interactive systems, especially ones that\nmodel interactions between independent entities.\n\nFor instance, consider once\nmore the implementation of a banking system that permits joint bank accounts.\n\nIn a conventional system using assignment and objects, we would model the\nfact that Peter and Paul share an account by having both Peter and Paul send\ntheir transaction requests to the same bank-account object, as we saw in\nsection.\n\nFrom the stream point\nof view, where there are no objects per se , we have\nalready indicated that a bank account can be modeled as a process that\noperates on a stream of transaction requests to produce a stream of\nresponses.\n\nAccordingly, we could model the fact that Peter and Paul have a\njoint bank account by merging Peter s stream of transaction requests\nwith Paul s stream of requests and feeding the result to the\nbank-account stream process, as shown in\nfigure.\n\nA joint\n\nThe trouble with this formulation is in the notion of merge.\n\nIt\nwill not do to merge the two streams by simply taking alternately one\nrequest from Peter and one request from Paul.\n\nSuppose Paul accesses\nthe account only very rarely.", - "token_count": 249, + "content": "On the other hand, if we look closely, we can see time-related problems\ncreeping into functional models as well.\n\nFor instance, consider once\nmore the implementation of a banking system that permits joint bank accounts.\n\nIn a conventional system using assignment and objects, we would model the\nfact that Peter and Paul share an account by having both Peter and Paul send\ntheir transaction requests to the same bank-account object, as we saw in\nsection.\n\nFrom the stream point\nof view, where there are no objects per se , we have\nalready indicated that a bank account can be modeled as a process that\noperates on a stream of transaction requests to produce a stream of\nresponses.\n\nAccordingly, we could model the fact that Peter and Paul have a\njoint bank account by merging Peter s stream of transaction requests\nwith Paul s stream of requests and feeding the result to the\nbank-account stream process, as shown in\nfigure.\n\nA joint\nbank account, modeled by merging two streams of transaction\nrequests.\n\nThe trouble with this formulation is in the notion of merge.\n\nIt\nwill not do to merge the two streams by simply taking alternately one\nrequest from Peter and one request from Paul.\n\nSuppose Paul accesses\nthe account only very rarely.\n\nWe could hardly force Peter to wait for\nPaul to access the account before he could issue a second transaction.\n\nHowever such a merge is implemented, it must interleave the two\ntransaction streams in some way that is constrained by real\ntime as perceived by Peter and Paul, in the sense that, if Peter and\nPaul meet, they can agree that certain transactions were processed\nbefore the meeting, and other transactions were processed after the\nmeeting.", + "token_count": 288, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3810,8 +5070,8 @@ "chunk_id": "Modularity_Objects_and_State_Modularity_of_Functional_Programs_and_Modularity_of_Objects_5" }, { - "content": "Suppose Paul accesses\nthe account only very rarely.\n\nWe could hardly force Peter to wait for\nPaul to access the account before he could issue a second transaction.\nreal\ntime as perceived by Peter and Paul, in the sense that, if Peter and\nPaul meet, they can agree that certain transactions were processed\nbefore the meeting, and other transactions were processed after the\nmeeting. , where we found the need to\nintroduce explicit synchronization to ensure a correct order\nof events in concurrent processing of objects with state.\n\nThus, in an\nattempt to support the functional style, the need to merge inputs from\ndifferent agents reintroduces the same problems that the functional style\nwas meant to eliminate.\n\nWe began this chapter with the goal of building computational models\nwhose structure matches our perception of the real world we are trying\nto model.\n\nWe can model the world as a collection of separate,\ntime-bound, interacting objects with state, or we can model the world\nas a single, timeless, stateless unity.\n\nEach view has powerful\nadvantages, but neither view alone is completely satisfactory.\n\nA\ngrand unification has yet to emerge.", - "token_count": 188, + "content": "However such a merge is implemented, it must interleave the two\ntransaction streams in some way that is constrained by real\ntime as perceived by Peter and Paul, in the sense that, if Peter and\nPaul meet, they can agree that certain transactions were processed\nbefore the meeting, and other transactions were processed after the\nmeeting.\n\nThus, in an\nattempt to support the functional style, the need to merge inputs from\ndifferent agents reintroduces the same problems that the functional style\nwas meant to eliminate.\n\nWe began this chapter with the goal of building computational models\nwhose structure matches our perception of the real world we are trying\nto model.\n\nWe can model the world as a collection of separate,\ntime-bound, interacting objects with state, or we can model the world\nas a single, timeless, stateless unity.\n\nEach view has powerful\nadvantages, but neither view alone is completely satisfactory.\n\nA\ngrand unification has yet to emerge.", + "token_count": 156, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3820,8 +5080,8 @@ "chunk_id": "Modularity_Objects_and_State_Modularity_of_Functional_Programs_and_Modularity_of_Objects_6" }, { - "content": "As we saw in\nsection ,\nsequences can serve as standard interfaces for combining program\nmodules.\n\nWe formulated powerful abstractions for manipulating\nsequences, such as\n\nUnfortunately, if we represent sequences as lists, this elegance is\nbought at the price of severe inefficiency with respect to both the\ntime and space required by our computations.\n\nWhen we represent manipulations on sequences as transformations\nof lists, our programs must construct and copy data structures (which\nmay be huge) at every step of a process.\n\nTo see why this is true, let us compare two programs for computing the\nsum of all the prime numbers in an interval.\n\nThe first program is\nwritten in standard iterative style:\n\n```javascript\nprime_definition\n sum_primes1\n sum_primes1_example\n 3437\n\nfunction sum_primes(a, b) {\n function iter(count, accum) {\n return count > b\n ? accum\n : is_prime(count)\n ? iter(count + 1, count + accum)\n : iter(count + 1, accum);\n }\n return iter(a, 0);\n}\n```\n\n```javascript\nsum_primes1_example\n\nsum_primes(7, 182);\n```\n\nThe second program performs the same computation using the sequence operations of section :\n\n```javascript\nsum_primes2\n prime_definition\n enumerate_interval\n sum_primes1_example\n 3437\n\nfunction sum_primes(a, b) {\n return accumulate((x, y) => x + y,\n 0,\n filter(is_prime,\n enumerate_interval(a, b)));\n}\n```\n\nIn carrying out the computation, the first program needs to store only\nthe sum being accumulated.\n\nIn contrast, the filter in the second\nprogram cannot do any testing until\nenumerate_interval\nhas constructed a complete list of the numbers in the interval.\n\nThe filter generates another list, which in turn is passed to\n\nThe inefficiency in using lists becomes painfully apparent if we use the sequence paradigm to compute the second prime in the interval from 10,000 to\n\n1,000,000 by evaluating the expression\n\n```javascript\npainfully\n prime_definition\n enumerate_interval\n\nhead(tail(filter(is_prime,\n enumerate_interval(10000, 1000000))));\n```\n\nThis expression does find the second prime, but the computational overhead\nis outrageous.", - "token_count": 298, + "content": "As we saw in\nsection ,\nsequences can serve as standard interfaces for combining program\nmodules.\n\nWe formulated powerful abstractions for manipulating\nsequences, such as ,\n, and\n, that capture a wide variety of\noperations in a manner that is both succinct and elegant.\n\nUnfortunately, if we represent sequences as lists, this elegance is\nbought at the price of severe inefficiency with respect to both the\ntime and space required by our computations.\n\nWhen we represent manipulations on sequences as transformations\nof lists, our programs must construct and copy data structures (which\nmay be huge) at every step of a process.\n\nTo see why this is true, let us compare two programs for computing the\nsum of all the prime numbers in an interval.\n\nThe first program is\nwritten in standard iterative style:\n\n```javascript\nprime_definition\n sum_primes1\n sum_primes1_example\n 3437\n\nfunction sum_primes(a, b) {\n function iter(count, accum) {\n return count > b\n ? accum\n : is_prime(count)\n ? iter(count + 1, count + accum)\n : iter(count + 1, accum);\n }\n return iter(a, 0);\n}\n```\n\n```javascript\nsum_primes1_example\n\nsum_primes(7, 182);\n```\n\nThe second program performs the same computation using the sequence operations of section :\n\n```javascript\nsum_primes2\n prime_definition\n enumerate_interval\n sum_primes1_example\n 3437\n\nfunction sum_primes(a, b) {\n return accumulate((x, y) => x + y,\n 0,\n filter(is_prime,\n enumerate_interval(a, b)));\n}\n```\n\nIn carrying out the computation, the first program needs to store only\nthe sum being accumulated.\n\nIn contrast, the filter in the second\nprogram cannot do any testing until\nenumerate_interval\nhas constructed a complete list of the numbers in the interval.\n\nThe filter generates another list, which in turn is passed to\nbefore being collapsed to form\na sum.\n\nSuch large intermediate storage is not needed by the first program,\nwhich we can think of as enumerating the interval incrementally, adding\neach prime to the sum as it is generated.", + "token_count": 305, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3830,9 +5090,9 @@ "chunk_id": "Modularity_Objects_and_State_Streams_Are_Delayed_Lists_1" }, { - "content": "This expression does find the second prime, but the computational overhead\nis outrageous.\n\nWe construct a list of almost a million integers, filter\nthis list by testing each element for primality, and then ignore almost\nall of the result.\n\nIn a more traditional programming style, we would\ninterleave the enumeration and the filtering, and stop when we reached\nthe second prime.\n\nStreams are a clever idea that allows one to use sequence\nmanipulations without incurring the costs of manipulating sequences as\nlists.\n\nWith streams we can achieve the best of both worlds: We can\nformulate programs elegantly as sequence manipulations, while attaining\nthe efficiency of incremental computation.\n\nThe basic idea is to arrange\nto construct a stream only partially, and to pass the partial\nconstruction to the program that consumes the stream.\n\nIf the consumer\nattempts to access a part of the stream that has not yet been\nconstructed, the stream will automatically construct just enough more\nof itself to produce the required part, thus preserving the illusion\nthat the entire stream exists.\n\nIn other words, although we will write\nprograms as if we were processing complete sequences, we design our\nstream implementation to automatically and transparently interleave\nthe construction of the stream with its use.\n\nTo accomplish this, we will construct streams using pairs,\nwith the first item of the stream in the head of the pair.\n\nHowever, rather than placing the value of the rest of the stream\npromise\nto compute the rest if it is ever requested.", - "token_count": 250, - "has_code": false, + "content": "Such large intermediate storage is not needed by the first program,\nwhich we can think of as enumerating the interval incrementally, adding\neach prime to the sum as it is generated.\n\n1,000,000 by evaluating the expression\n\n```javascript\npainfully\n prime_definition\n enumerate_interval\n\nhead(tail(filter(is_prime,\n enumerate_interval(10000, 1000000))));\n```\n\nThis expression does find the second prime, but the computational overhead\nis outrageous.\n\nWe construct a list of almost a million integers, filter\nthis list by testing each element for primality, and then ignore almost\nall of the result.\n\nIn a more traditional programming style, we would\ninterleave the enumeration and the filtering, and stop when we reached\nthe second prime.\n\nStreams are a clever idea that allows one to use sequence\nmanipulations without incurring the costs of manipulating sequences as\nlists.\n\nWith streams we can achieve the best of both worlds: We can\nformulate programs elegantly as sequence manipulations, while attaining\nthe efficiency of incremental computation.\n\nThe basic idea is to arrange\nto construct a stream only partially, and to pass the partial\nconstruction to the program that consumes the stream.\n\nIf the consumer\nattempts to access a part of the stream that has not yet been\nconstructed, the stream will automatically construct just enough more\nof itself to produce the required part, thus preserving the illusion\nthat the entire stream exists.\n\nIn other words, although we will write\nprograms as if we were processing complete sequences, we design our\nstream implementation to automatically and transparently interleave\nthe construction of the stream with its use.\n\nTo accomplish this, we will construct streams using pairs,\nwith the first item of the stream in the head of the pair.", + "token_count": 272, + "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", "subsection": "Streams Are Delayed Lists", @@ -3840,8 +5100,8 @@ "chunk_id": "Modularity_Objects_and_State_Streams_Are_Delayed_Lists_2" }, { - "content": "However, rather than placing the value of the rest of the stream\npromise\nto compute the rest if it is ever requested.\n\nIf we have a data item\nh and a stream\nt , we construct a stream\nwhose head is\nh and whose tail is\nt by evaluating\npair(h, () => t) the\ntail\nt of a stream is\nwrapped in a function of no arguments,\ndelayed.\nnull , the same as the empty list.\n\nTo access the first data item of a nonempty stream,\nwe simply select the\nhead of the pair, as with a list.\n\nBut to access the tail of a stream, we need to evaluate the\ndelayed expression.\n\nFor convenience, we define\n\n```javascript\nstream_tail\n\t stream_tail_example\n\t 5\n\nfunction stream_tail(stream) {\n return tail(stream)();\n}\n```\n\n```javascript\nstream_tail_example\n\nstream_tail(pair(4, () => pair(5, () => null)));\n\nhead(stream_tail(pair(4, () => pair(5, () => null))));\n```\n\nThis selects the tail of the pair and applies the function found there to obtain the next pair of the stream (or null if the\n\ntail of the stream is empty) in effect, forcing the function in the tail of the pair to fulfill its promise.\n\nWe can make and use streams, in just the same way as we can make\nand use lists, to represent aggregate data arranged in a sequence.\n\nIn\nparticular, we can build stream analogs of the list operations from\nchapter , such as list_ref ,\nfor_each :\n\n```javascript\nstream_tail\n stream_functions\n stream_functions_example\n\nfunction stream_ref(s, n) {\n return n === 0\n ? head(s)\n : stream_ref(stream_tail(s), n - 1);\n}\nfunction stream_map(f, s) {\n return is_null(s)\n ? null\n : pair(f(head(s)),\n () => stream_map(f, stream_tail(s)));\n}\nfunction stream_for_each(fun, s) {\n if (is_null(s)) {\n return true;\n } else {\n fun(head(s));\n return stream_for_each(fun, stream_tail(s));\n }\n}\n```", - "token_count": 291, + "content": "To accomplish this, we will construct streams using pairs,\nwith the first item of the stream in the head of the pair.\n\nIf we have a data item\nh and a stream\nt , we construct a stream\nwhose head is\nh and whose tail is\nt by evaluating\npair(h, () => t) the\ntail\nt of a stream is\nwrapped in a function of no arguments,\nso that its evaluation will be delayed.\n\nThe empty stream is\nnull , the same as the empty list.\n\nTo access the first data item of a nonempty stream,\nwe simply select the\nhead of the pair, as with a list.\n\nBut to access the tail of a stream, we need to evaluate the\ndelayed expression.\n\nFor convenience, we define\n\n```javascript\nstream_tail\n\t stream_tail_example\n\t 5\n\nfunction stream_tail(stream) {\n return tail(stream)();\n}\n```\n\n```javascript\nstream_tail_example\n\nstream_tail(pair(4, () => pair(5, () => null)));\n\nhead(stream_tail(pair(4, () => pair(5, () => null))));\n```\n\nThis selects the tail of the pair and applies the function found there to obtain the next pair of the stream (or null if the\n\ntail of the stream is empty) in effect, forcing the function in the tail of the pair to fulfill its promise.\n\nWe can make and use streams, in just the same way as we can make\nand use lists, to represent aggregate data arranged in a sequence.\n\nIn\nparticular, we can build stream analogs of the list operations from\nchapter , such as list_ref ,\n, and\nfor_each :\n\n```javascript\nstream_tail\n stream_functions\n stream_functions_example\n\nfunction stream_ref(s, n) {\n return n === 0\n ? head(s)\n : stream_ref(stream_tail(s), n - 1);\n}\nfunction stream_map(f, s) {\n return is_null(s)\n ? null\n : pair(f(head(s)),\n () => stream_map(f, stream_tail(s)));\n}\nfunction stream_for_each(fun, s) {\n if (is_null(s)) {\n return true;\n } else {\n fun(head(s));\n return stream_for_each(fun, stream_tail(s));\n }\n}\n```", + "token_count": 303, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3850,8 +5110,8 @@ "chunk_id": "Modularity_Objects_and_State_Streams_Are_Delayed_Lists_3" }, { - "content": "In\nparticular, we can build stream analogs of the list operations from\nchapter , such as list_ref ,\nfor_each :\n\n```javascript\nstream_tail\n stream_functions_example\n\nconst my_stream = pair(4, () => pair(5, () => null));\ndisplay(stream_ref(my_stream, 1));\nconst my_stream_2 = stream_map(x => x + 1, my_stream);\nstream_for_each(display, my_stream_2);\n\nconst my_stream = pair(4, () => pair(5, () => null));\nconst my_stream_2 = stream_map(x => x + 1, my_stream);\nlet acc = 0;\nstream_for_each(x => {acc = acc + x;}, my_stream_2);\nacc;\n```\n\nThe function stream_for_each is useful for viewing streams:\n\n```javascript\ndisplay_stream\n display_stream_example\n\nfunction display_stream(s) {\n return stream_for_each(display, s);\n}\n\nconst max_display = 9;\nfunction display_stream(s) {\n function display_stream_iter(st, n) {\n if (is_null(st)) {\n } else if (n === 0) {\n display('', \"...\");\n } else {\n display(head(st));\n display_stream_iter(stream_tail(st), n - 1);\n }\n }\n display_stream_iter(s, max_display);\n}\n```\n\n```javascript\nstream_tail\n\t display_stream\n display_stream_example\n\nconst my_stream = pair(4, () => pair(5, () => null));\ndisplay_stream(my_stream);\n```\n\nTo make the stream implementation automatically and transparently\ninterleave the construction of a stream with its use, we have arranged\nfor the tail\nof a stream to be evaluated when it is accessed by the\nstream_tail\nfunction rather than when the stream is constructed by\npair.\n\nThis implementation choice is reminiscent of our discussion of rational numbers\nin section , where we saw\nthat we can choose to implement rational numbers so that the reduction\nof numerator and denominator to lowest terms is performed either at\nconstruction time or at selection time.\n\nThe two rational-number\nimplementations produce the same data abstraction, but the choice has\nan effect on efficiency.\n\nThere is a similar relationship between\nstreams and ordinary lists.\n\nAs a data abstraction, streams are the\nsame as lists.\n\nThe difference is the time at which the elements are\nevaluated.\n\nWith ordinary lists, both the\nhead and the\ntail\nare evaluated at construction time.", - "token_count": 304, + "content": "In\nparticular, we can build stream analogs of the list operations from\nchapter , such as list_ref ,\n, and\nfor_each :\n\nThe function stream_for_each is useful for viewing streams:\n\n```javascript\ndisplay_stream\n display_stream_example\n\nfunction display_stream(s) {\n return stream_for_each(display, s);\n}\n\nconst max_display = 9;\nfunction display_stream(s) {\n function display_stream_iter(st, n) {\n if (is_null(st)) {\n } else if (n === 0) {\n display('', \"...\");\n } else {\n display(head(st));\n display_stream_iter(stream_tail(st), n - 1);\n }\n }\n display_stream_iter(s, max_display);\n}\n```\n\n```javascript\nstream_tail\n\t display_stream\n display_stream_example\n\nconst my_stream = pair(4, () => pair(5, () => null));\ndisplay_stream(my_stream);\n```\n\nTo make the stream implementation automatically and transparently\ninterleave the construction of a stream with its use, we have arranged\nfor the tail\nof a stream to be evaluated when it is accessed by the\nstream_tail\nfunction rather than when the stream is constructed by\npair.\n\nThis implementation choice is reminiscent of our discussion of rational numbers\nin section , where we saw\nthat we can choose to implement rational numbers so that the reduction\nof numerator and denominator to lowest terms is performed either at\nconstruction time or at selection time.\n\nThe two rational-number\nimplementations produce the same data abstraction, but the choice has\nan effect on efficiency.\n\nThere is a similar relationship between\nstreams and ordinary lists.\n\nAs a data abstraction, streams are the\nsame as lists.\n\nThe difference is the time at which the elements are\nevaluated.\n\nWith ordinary lists, both the\nhead and the\ntail\nare evaluated at construction time.\n\nWith streams, the\ntail is evaluated at selection time.\n\nTo see how this data structure behaves, let us analyze the outrageous prime computation we saw above, reformulated in terms of streams:\n\n```javascript\nno_more_outrageous\n prime_definition\n stream_enumerate_interval\n\t 10009\n\nhead(stream_tail(stream_filter(\n is_prime,\n stream_enumerate_interval(10000, 1000000))));\n```\n\nWe will see that it does indeed work efficiently.", + "token_count": 297, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3860,8 +5120,8 @@ "chunk_id": "Modularity_Objects_and_State_Streams_Are_Delayed_Lists_4" }, { - "content": "With ordinary lists, both the\nhead and the\ntail\nare evaluated at construction time.\n\nWith streams, the\ntail is evaluated at selection time.\n\nTo see how this data structure behaves, let us analyze the outrageous prime computation we saw above, reformulated in terms of streams:\n\n```javascript\nno_more_outrageous\n prime_definition\n stream_enumerate_interval\n\t 10009\n\nhead(stream_tail(stream_filter(\n is_prime,\n stream_enumerate_interval(10000, 1000000))));\n```\n\nWe will see that it does indeed work efficiently.\n\nWe begin by calling\nstream_enumerate_interval with\nthe arguments 10,000 and 1,000,000.\n\nThe function\nstream_enumerate_interval\nis the stream analog of\nenumerate_interval\n(section ):\n\n```javascript\nstream_enumerate_interval_example\n stream_enumerate_interval\n\nstream_enumerate_interval(10000, 1000000);\n\nstream_ref(stream_enumerate_interval(10000, 1000000), 100);\n```\n\n```javascript\nprime_definition\n stream_enumerate_interval\n\t stream_enumerate_interval_example\n\t 10100\n\nfunction stream_enumerate_interval(low, high) {\n return low > high\n ? null\n : pair(low,\n () => stream_enumerate_interval(low + 1, high));\n}\n```\n\nand thus the result returned by stream_enumerate_interval , formed by the\n\n```javascript\nstream_enumerate_interval_example_2\n stream_enumerate_interval\n\t 10100\n\npair(10000, () => stream_enumerate_interval(10001, 1000000));\n\nstream_ref(pair(10000, () => stream_enumerate_interval(10001, 1000000)), 100);\n```\n\nThat is, stream_enumerate_interval\nreturns a stream represented as a pair whose\nhead\nis 10,000 and whose tail\nis a promise to enumerate more of the\ninterval if so requested.\n\nThis stream is now filtered for primes,\nusing the stream analog of the ):\n\n```javascript\nstream_tail\n stream_filter\n stream_filter_example\n\t 6\n\nfunction stream_filter(pred, stream) {\n return is_null(stream)\n ? null\n : pred(head(stream))\n ? pair(head(stream),\n () => stream_filter(pred, stream_tail(stream)))\n : stream_filter(pred, stream_tail(stream));\n}\n```\n\n```javascript\nstream_filter_example\n display_stream\n\nconst my_stream = pair(5, () => pair(6, () => pair(7, () => null)));\nconst my_filtered_stream =\n stream_filter(x => x % 2 === 0, my_stream);\ndisplay_stream(my_filtered_stream);\n\nconst my_stream = pair(5, () => pair(6, () => pair(7, () => null)));\nconst my_filtered_stream =\n stream_filter(x => x % 2 === 0, my_stream);\nhead(my_filtered_stream);\n```\n\nThe function stream_filter tests the\nstream_filter\nexamines the tail of its input stream.\n\nThe call to\nstream_tail forces evaluation of the\ndelayed stream_enumerate_interval ,\nwhich now returns\n\n```javascript\nenumerate_interval_example_3\n stream_enumerate_interval\n\t 10101\n\npair(10001, () => stream_enumerate_interval(10002, 1000000));\n\nstream_ref(pair(10001, () => stream_enumerate_interval(10002, 1000000)), 100);\n```", - "token_count": 313, + "content": "We will see that it does indeed work efficiently.\n\nThe function\nstream_enumerate_interval\nis the stream analog of\nenumerate_interval\n(section ):\n\n```javascript\nstream_enumerate_interval_example\n stream_enumerate_interval\n\nstream_enumerate_interval(10000, 1000000);\n\nstream_ref(stream_enumerate_interval(10000, 1000000), 100);\n```\n\n```javascript\nprime_definition\n stream_enumerate_interval\n\t stream_enumerate_interval_example\n\t 10100\n\nfunction stream_enumerate_interval(low, high) {\n return low > high\n ? null\n : pair(low,\n () => stream_enumerate_interval(low + 1, high));\n}\n```\n\nand thus the result returned by stream_enumerate_interval , formed by the , is\n\n```javascript\nstream_enumerate_interval_example_2\n stream_enumerate_interval\n\t 10100\n\npair(10000, () => stream_enumerate_interval(10001, 1000000));\n\nstream_ref(pair(10000, () => stream_enumerate_interval(10001, 1000000)), 100);\n```\n\nThat is, stream_enumerate_interval\nreturns a stream represented as a pair whose\nhead\nis 10,000 and whose tail\nis a promise to enumerate more of the\ninterval if so requested.\n\nThis stream is now filtered for primes,\nusing the stream analog of the\nfunction\n(section ):\n\n```javascript\nstream_tail\n stream_filter\n stream_filter_example\n\t 6\n\nfunction stream_filter(pred, stream) {\n return is_null(stream)\n ? null\n : pred(head(stream))\n ? pair(head(stream),\n () => stream_filter(pred, stream_tail(stream)))\n : stream_filter(pred, stream_tail(stream));\n}\n```\n\n```javascript\nstream_filter_example\n display_stream\n\nconst my_stream = pair(5, () => pair(6, () => pair(7, () => null)));\nconst my_filtered_stream =\n stream_filter(x => x % 2 === 0, my_stream);\ndisplay_stream(my_filtered_stream);\n\nconst my_stream = pair(5, () => pair(6, () => pair(7, () => null)));\nconst my_filtered_stream =\n stream_filter(x => x % 2 === 0, my_stream);\nhead(my_filtered_stream);\n```\n\nThe function stream_filter tests the\nof the stream (which is 10,000).\n\nSince\nthis is not prime, stream_filter\nexamines the tail of its input stream.\n\nThe call to\nstream_tail forces evaluation of the\ndelayed stream_enumerate_interval ,\nwhich now returns\n\n```javascript\nenumerate_interval_example_3\n stream_enumerate_interval\n\t 10101\n\npair(10001, () => stream_enumerate_interval(10002, 1000000));\n\nstream_ref(pair(10001, () => stream_enumerate_interval(10002, 1000000)), 100);\n```\n\nThe function stream_filter now looks at the head of this stream, 10,001, sees that this is not prime either, forces another stream_@tail , and so\n\non, until stream_@enumerate_interval yields the prime 10,007, whereupon stream_@filter , according to its definition, returns\n\n```javascript\npair(head(stream),\n () => stream_filter(pred, stream_tail(stream)));\n```\n\nwhich in this case is\n\n```javascript\nwhich_in_this_case\n prime_definition\n stream_enumerate_interval\n\t 10949\n\npair(10007,\n () => stream_filter(\n is_prime,\n pair(10008,\n () => stream_enumerate_interval(10009, 1000000))));\n\nstream_ref(pair(10007,\n () => stream_filter(\n is_prime,\n pair(10008,\n () => stream_enumerate_interval(10009, 1000000))\n )\n ), 100);\n```", + "token_count": 343, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3870,8 +5130,8 @@ "chunk_id": "Modularity_Objects_and_State_Streams_Are_Delayed_Lists_5" }, { - "content": "The call to\nstream_tail forces evaluation of the\ndelayed stream_enumerate_interval ,\nwhich now returns\n\nThe function stream_filter now looks at the head of this stream, 10,001, sees that this is not prime either, forces another stream_@tail , and so\n\non, until stream_@enumerate_interval yields the prime 10,007, whereupon stream_@filter , according to its definition, returns\n\n```javascript\npair(head(stream),\n () => stream_filter(pred, stream_tail(stream)));\n```\n\nwhich in this case is\n\n```javascript\nwhich_in_this_case\n prime_definition\n stream_enumerate_interval\n\t 10949\n\npair(10007,\n () => stream_filter(\n is_prime,\n pair(10008,\n () => stream_enumerate_interval(10009, 1000000))));\n\nstream_ref(pair(10007,\n () => stream_filter(\n is_prime,\n pair(10008,\n () => stream_enumerate_interval(10009, 1000000))\n )\n ), 100);\n```\n\nThis result is now passed to\nstream_tail in our original\nexpression.\n\nThis forces the delayed\nstream_filter ,\nwhich in turn keeps forcing the delayed\nstream_@enumerate_interval until it\nfinds the next prime, which is 10,009.\n\nFinally, the result passed to\n\n```javascript\nnow_passed_to\n prime_definition\n stream_enumerate_interval\n\t 10957\n\npair(10009,\n () => stream_filter(\n is_prime,\n pair(10010,\n () => stream_enumerate_interval(10011, 1000000))));\n\nstream_ref(pair(10009,\n () => stream_filter(\n is_prime,\n pair(10010,\n () => stream_enumerate_interval(10011, 1000000))\n )\n ), 100);\n```\n\nThe function\n\nIn general, we can think of delayed evaluation as\ndemand-driven programming, whereby each stage in the\nstream process is activated only enough to satisfy the next stage.\n\nWhat\nwe have done is to\nall at once when, in reality, the computation is\nperformed incrementally, as in traditional programming styles.\n\nWhen we construct stream pairs, we delay the evaluation of their tail\nexpressions by wrapping these expressions in a function.\n\nWe force their\nevaluation when needed, by applying the function.\n\nThis implementation suffices for streams to work as advertised, but\nthere is an important optimization that we shall consider where needed.\n\nIn many applications, we end up forcing the same delayed object many\ntimes.\n\nThis can lead to serious inefficiency in recursive programs\ninvolving streams.", - "token_count": 289, + "content": "which in this case is\n\nThis forces the delayed\nstream_filter ,\nwhich in turn keeps forcing the delayed\nstream_@enumerate_interval until it\nfinds the next prime, which is 10,009.\n\nFinally, the result passed to\nin our original expression is\n\n```javascript\nnow_passed_to\n prime_definition\n stream_enumerate_interval\n\t 10957\n\npair(10009,\n () => stream_filter(\n is_prime,\n pair(10010,\n () => stream_enumerate_interval(10011, 1000000))));\n\nstream_ref(pair(10009,\n () => stream_filter(\n is_prime,\n pair(10010,\n () => stream_enumerate_interval(10011, 1000000))\n )\n ), 100);\n```\n\nThe function returns 10,009, and the\ncomputation is complete.\n\nOnly as many integers were tested for\nprimality as were necessary to find the second prime, and the interval\nwas enumerated only as far as was necessary to feed the prime filter.\n\nIn general, we can think of delayed evaluation as\ndemand-driven programming, whereby each stage in the\nstream process is activated only enough to satisfy the next stage.\n\nWhat\nwe have done is to\ndecouple the actual order of events in the computation from the apparent\nstructure of our functions.\n\nWe write functions as if the streams existed\nall at once when, in reality, the computation is\nperformed incrementally, as in traditional programming styles.\n\nWhen we construct stream pairs, we delay the evaluation of their tail\nexpressions by wrapping these expressions in a function.\n\nWe force their\nevaluation when needed, by applying the function.\n\nThis implementation suffices for streams to work as advertised, but\nthere is an important optimization that we shall consider where needed.\n\nIn many applications, we end up forcing the same delayed object many\ntimes.\n\nThis can lead to serious inefficiency in recursive programs\ninvolving streams.\n\n(See\nexercise.)\nThe solution is to build delayed objects so that the first time they are\nforced, they store the value that is computed.\n\nSubsequent forcings will\nsimply return the stored value without repeating the computation.", + "token_count": 292, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3880,8 +5140,8 @@ "chunk_id": "Modularity_Objects_and_State_Streams_Are_Delayed_Lists_6" }, { - "content": "This can lead to serious inefficiency in recursive programs\ninvolving streams.\n\n(See\nexercise.)\nThe solution is to build delayed objects so that the first time they are\nforced, they store the value that is computed.\n\nSubsequent forcings will\nsimply return the stored value without repeating the computation.\n\nIn\nother words, we implement the construction of stream pairs as a.\n\nOne way to accomplish this\nis to use the following function, which takes as argument a function\n(of no arguments) and returns a memoized version of the function.\n\nThe first time the memoized function is run, it saves the computed\nresult.\n\nOn subsequent evaluations, it simply returns\nthe result.\n\n```javascript\nmemo\n\t memo_example\n\t 1\n\nfunction memo(fun) {\n let already_run = false;\n let result = undefined;\n return () => {\n if (!already_run) {\n result = fun();\n already_run = true;\n return result;\n } else {\n return result;\n }\n };\n}\n```\n\n```javascript\nmemo_example\n\nfunction square_4() {\n const result = 4 * 4;\n display(\"multiplication carried out\");\n return result;\n}\nconst memo_square_4 = memo(square_4);\ndisplay(memo_square_4()); // shows \"multipl..\"\ndisplay(memo_square_4()); // does not show \"multipl..\"\n\nlet calls = 0;\nfunction square_4() {\n const result = 4 * 4;\n calls = calls + 1;\n return result;\n}\nconst memo_square_4 = memo(square_4);\nmemo_square_4();\nmemo_square_4();\ncalls;\n```\n\nWe can make use of memo whenever\nwe construct a stream pair.\n\nFor example, instead of\n\n```javascript\nstream_map_example\n\t 3\n\nfunction stream_map(f, s) {\n return is_null(s)\n ? null\n : pair(f(head(s)),\n () => stream_map(f, stream_tail(s)));\n}\n```", - "token_count": 242, + "content": "Subsequent forcings will\nsimply return the stored value without repeating the computation.\n\nOne way to accomplish this\nis to use the following function, which takes as argument a function\n(of no arguments) and returns a memoized version of the function.\n\nThe first time the memoized function is run, it saves the computed\nresult.\n\nOn subsequent evaluations, it simply returns\nthe result.\n\n```javascript\nmemo\n\t memo_example\n\t 1\n\nfunction memo(fun) {\n let already_run = false;\n let result = undefined;\n return () => {\n if (!already_run) {\n result = fun();\n already_run = true;\n return result;\n } else {\n return result;\n }\n };\n}\n```\n\n```javascript\nmemo_example\n\nfunction square_4() {\n const result = 4 * 4;\n display(\"multiplication carried out\");\n return result;\n}\nconst memo_square_4 = memo(square_4);\ndisplay(memo_square_4()); // shows \"multipl..\"\ndisplay(memo_square_4()); // does not show \"multipl..\"\n\nlet calls = 0;\nfunction square_4() {\n const result = 4 * 4;\n calls = calls + 1;\n return result;\n}\nconst memo_square_4 = memo(square_4);\nmemo_square_4();\nmemo_square_4();\ncalls;\n```\n\nWe can make use of memo whenever\nwe construct a stream pair.\n\nFor example, instead of\n\n```javascript\nstream_map_example\n\t 3\n\nfunction stream_map(f, s) {\n return is_null(s)\n ? null\n : pair(f(head(s)),\n () => stream_map(f, stream_tail(s)));\n}\n```\n\n```javascript\nstream_tail\n\t stream_map_example\n\nconst my_stream = pair(4, () => pair(5, () => null));\n\nconst my_stream_2 =\n stream_map(display, my_stream);\n\nstream_ref(my_stream_2, 1);\nstream_ref(my_stream_2, 1);\n// the number 5 is shown twice\n// because the same delayed\n// object is forced twice\n\nconst my_stream = pair(4, () => pair(5, () => null));\nlet calls = 0;\nconst my_stream_2 =\n stream_map(x => {calls = calls + 1;}, my_stream);\n\nstream_ref(my_stream_2, 1);\nstream_ref(my_stream_2, 1);\ncalls;\n```\n\nwe can define an optimized function stream_map as follows:\n\n```javascript\nstream_map_optimized\n\t memo\n\t stream_map_optimized_example\n\t 2\n\nfunction stream_map_optimized(f, s) {\n return is_null(s)\n ? null\n : pair(f(head(s)),\n memo(() =>\n stream_map_optimized(f, stream_tail(s))));\n}\n```", + "token_count": 295, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3890,9 +5150,9 @@ "chunk_id": "Modularity_Objects_and_State_Streams_Are_Delayed_Lists_7" }, { - "content": "For example, instead of\n\n```javascript\nstream_tail\n\t stream_map_example\n\nconst my_stream = pair(4, () => pair(5, () => null));\n\nconst my_stream_2 =\n stream_map(display, my_stream);\n\nstream_ref(my_stream_2, 1);\nstream_ref(my_stream_2, 1);\n// the number 5 is shown twice\n// because the same delayed\n// object is forced twice\n\nconst my_stream = pair(4, () => pair(5, () => null));\nlet calls = 0;\nconst my_stream_2 =\n stream_map(x => {calls = calls + 1;}, my_stream);\n\nstream_ref(my_stream_2, 1);\nstream_ref(my_stream_2, 1);\ncalls;\n```\n\nwe can define an optimized function stream_map as follows:\n\n```javascript\nstream_map_optimized\n\t memo\n\t stream_map_optimized_example\n\t 2\n\nfunction stream_map_optimized(f, s) {\n return is_null(s)\n ? null\n : pair(f(head(s)),\n memo(() =>\n stream_map_optimized(f, stream_tail(s))));\n}\n```\n\n```javascript\nstream_map_optimized_example\n\nconst my_stream = pair(4, () => pair(5, () => null));\n\nconst my_stream_2 =\n stream_map(display, my_stream);\n\nstream_ref(my_stream_2, 1);\nstream_ref(my_stream_2, 1);\n// the number 5 is shown twice\n// because the same delayed\n// object is forced twice\n\nconst my_stream_3 =\n stream_map_optimized(display, my_stream);\n\nstream_ref(my_stream_3, 1);\nstream_ref(my_stream_3, 1);\n// the number 5 is shown only once\n// because the result of forcing\n// the delayed object is memoized\n\nconst my_stream = pair(4, () => pair(5, () => null));\n\nconst my_stream_2 =\n stream_map(x => x, my_stream);\n\nstream_ref(my_stream_2, 1);\nstream_ref(my_stream_2, 1);\n// the number 5 is shown twice\n// because the same delayed\n// object is forced twice\n\nlet calls = 0;\nconst my_stream_3 =\n stream_map_optimized(x => {calls = calls + 1;}, my_stream);\n\nstream_ref(my_stream_3, 1);\nstream_ref(my_stream_3, 1);\ncalls;\n```", - "token_count": 230, - "has_code": true, + "content": "we can define an optimized function stream_map as follows:", + "token_count": 9, + "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Streams", "subsection": "Streams Are Delayed Lists", @@ -3900,7 +5160,7 @@ "chunk_id": "Modularity_Objects_and_State_Streams_Are_Delayed_Lists_8" }, { - "content": "The function at the end of the preceding section shows how we can use streams to model signal-processing systems that contain is modeled by the\n\nfact that s internal stream integ is defined in terms of itself:\n\n```javascript\nconst integ = pair(initial_value,\n () => add_streams(scale_stream(integrand, dt),\n integ));\n```\n\n```javascript\nThe interpreters ability to deal with such an implicit definition\n\tdepends on the delay resulting from wrapping the call to\n\tadd_streams in a lambda expression.\n\tWithout this delay, the interpreter could not\n\tconstruct\n```\n\nUnfortunately, stream models of systems with loops may require uses of a\ndelay beyond the stream programming pattern seen so far.\n\nFor instance,\nfigure shows a\nsignal-processing system for solving the\n$dy/dt=f(y)$ where\n$f$ is a given function.\n\nThe figure shows a\nmapping component, which applies $f$ to its\ninput signal, linked in a feedback loop to an integrator in a manner\nvery similar to that of the analog computer circuits that are actually\nused to solve such equations.\n\nAn\nanalog computer circuit that solves the equation\n$dy/dt = f(y)$.\n\nAssuming we are given an initial value $y_0$ for $y$ , we could try to model this system using the function\n\n```javascript\nfunction solve(f, y0, dt) {\n const y = integral(dy, y0, dt);\n const dy = stream_map(f, y);\n return y;\n}\n```\n\nThis function does not work, because in the first line of\n\nOn the other hand, the intent of our definition does make sense, because we can, in principle, begin to generate the Indeed, For in the\n\nsecond line of\n\nTo take advantage of this idea, we will redefine\ndelayed argument.\n\nThe function\nthe integrand to be evaluated only when it is required to generate more than\nthe first element of the output stream:", + "content": "The\nfunction\nat the end of the preceding section shows how we can use streams to model\nsignal-processing systems that contain\nfeedback loops.\n\nThe feedback loop for the adder shown in\nfigure is modeled by the fact that\ns\ninternal stream\ninteg\nis defined in terms of itself:\n\n```javascript\nconst integ = pair(initial_value,\n () => add_streams(scale_stream(integrand, dt),\n integ));\n```\n\nThe interpreters ability to deal with such an implicit definition depends on the delay resulting from wrapping the call to add_streams in a lambda expression.\n\nWithout this delay, the interpreter could not construct before evaluating the call to , which would require that already be defined.\n\nIn general, such a delay is crucial for using streams to model signal-processing systems that contain loops.\n\nWithout a delay, our models would have to be formulated so that the inputs to any signal-processing component would be fully evaluated before the output could be produced.\n\nThis would outlaw loops.\n\nUnfortunately, stream models of systems with loops may require uses of a\ndelay beyond the stream programming pattern seen so far.\n\nFor instance,\nfigure shows a\nsignal-processing system for solving the\ndifferential equation $dy/dt=f(y)$ where\n$f$ is a given function.\n\nThe figure shows a\nmapping component, which applies $f$ to its\ninput signal, linked in a feedback loop to an integrator in a manner\nvery similar to that of the analog computer circuits that are actually\nused to solve such equations.\n\nAn\nanalog computer circuit that solves the equation\n$dy/dt = f(y)$.\n\nAssuming we are given an initial value $y_0$ for $y$ , we could try to model this system using the function\n\n```javascript\nfunction solve(f, y0, dt) {\n const y = integral(dy, y0, dt);\n const dy = stream_map(f, y);\n return y;\n}\n```", "token_count": 288, "has_code": true, "chapter": "Modularity, Objects, and State", @@ -3910,8 +5170,8 @@ "chunk_id": "Modularity_Objects_and_State_Streams_and_Delayed_Evaluation_1" }, { - "content": "The function\nthe integrand to be evaluated only when it is required to generate more than\nthe first element of the output stream:\n\n```javascript\nintegral_example_4\n\nfunction numbers_starting_from(t, dt) {\n return pair(t,\n () => numbers_starting_from(t + dt, dt)\n );\n}\nconst dt = 0.01;\nconst linear = numbers_starting_from(0, dt);\nconst linear_integral = integral(() => linear, 0, dt);\n// computing integral from 0 to 3 of f(x) = x\n// (the integral is g(x) = 0.5 x^2, and therefore\n// the result is near 0.5 * 3^2 = 4.5)\nstream_ref(linear_integral, math_round(3 / dt));\n```\n\n```javascript\nintegral\n add_streams\n scale_stream\n integral_example_4\n 4.484999999999992\n\nfunction integral(delayed_integrand, initial_value, dt) {\n const integ =\n pair(initial_value,\n () => {\n const integrand = delayed_integrand();\n return add_streams(scale_stream(integrand, dt),\n integ);\n });\n return integ;\n}\n```\n\nNow we can implement our function by delaying the evaluation of declaration of\n\n```javascript\nsolve\n e\n\nfunction solve(f, y0, dt) {\n const y = integral(() => dy, y0, dt);\n const dy = stream_map(f, y);\n return y;\n}\n```\n\nIn general, every caller of\ndelay\nthe integrand argument.\n\nWe can demonstrate that the\nfunction\nworks by approximating\n$e\\approx 2.718$ by computing the value at\n$y=1$ of the solution to the differential\nequation $dy/dt=y$ with initial condition\n$y(0)=1$ :", - "token_count": 200, + "content": "Assuming we are given an initial value $y_0$ for $y$ , we could try to model this system using the function\n\nthe second line of.\n\nOn the other hand, the intent of our definition does make sense, because we\ncan, in principle, begin to generate the\nstream without knowing.\n\nIndeed, and many other stream operations can generate part of the answer given only partial information about the arguments.\n\nFor , the first element of the output\nstream is the specified.\n\nThus,\nwe can generate the first element of the output stream without evaluating\nthe integrand.\n\nOnce we know the first\nelement of , the\nin the second line of can begin working\nto generate the first element of , which will\nproduce the next element of , and so on.\n\nTo take advantage of this idea, we will redefine\nto expect the integrand stream to be a\ndelayed argument.\n\nThe function will force\nthe integrand to be evaluated only when it is required to generate more than\nthe first element of the output stream:\n\n```javascript\nintegral_example_4\n\nfunction numbers_starting_from(t, dt) {\n return pair(t,\n () => numbers_starting_from(t + dt, dt)\n );\n}\nconst dt = 0.01;\nconst linear = numbers_starting_from(0, dt);\nconst linear_integral = integral(() => linear, 0, dt);\n// computing integral from 0 to 3 of f(x) = x\n// (the integral is g(x) = 0.5 x^2, and therefore\n// the result is near 0.5 * 3^2 = 4.5)\nstream_ref(linear_integral, math_round(3 / dt));\n```\n\n```javascript\nintegral\n add_streams\n scale_stream\n integral_example_4\n 4.484999999999992\n\nfunction integral(delayed_integrand, initial_value, dt) {\n const integ =\n pair(initial_value,\n () => {\n const integrand = delayed_integrand();\n return add_streams(scale_stream(integrand, dt),\n integ);\n });\n return integ;\n}\n```\n\nNow we can implement our function by delaying the evaluation of in the declaration of :", + "token_count": 290, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3920,8 +5180,8 @@ "chunk_id": "Modularity_Objects_and_State_Streams_and_Delayed_Evaluation_2" }, { - "content": "We can demonstrate that the\nfunction\nworks by approximating\n$e\\approx 2.718$ by computing the value at\n$y=1$ of the solution to the differential\nequation $dy/dt=y$ with initial condition\n$y(0)=1$ :\n\n```javascript\nsolve_optimized\n\nfunction memo(fun) {\n let already_run = false;\n let result = undefined;\n return () => {\n if (!already_run) {\n result = fun();\n already_run = true;\n return result;\n } else {\n return result;\n }\n\t };\n}\n// note the use of the memoization optimization\nfunction stream_combine(f, s1, s2) {\n return is_null(s1) && is_null(s2)\n ? null\n : is_null(s1) || is_null(s2)\n ? error(null, \"unexpected argument -- stream_combine\")\n : pair(f(head(s1), head(s2)),\n memo(() => stream_combine(f, stream_tail(s1),\n stream_tail(s2))));\n}\nfunction add_streams(s1, s2) {\n return stream_combine((x1, x2) => x1 + x2, s1, s2);\n}\nfunction scale_stream(stream, factor) {\n return stream_map(x => x * factor,\n stream);\n}\n// note the use of the memoization optimization\nfunction integral(delayed_integrand, initial_value, dt) {\n const integ =\n pair(initial_value,\n memo(() => {\n const integrand = delayed_integrand();\n return add_streams(scale_stream(integrand, dt),\n integ);\n }));\n return integ;\n}\nfunction solve(f, y0, dt) {\n const y = integral(() => dy, y0, dt);\n const dy = stream_map(f, y);\n return y;\n}\n```\n\n```javascript\ne\n solve_optimized\n 2.716923932235896\n\nstream_ref(solve(y => y, 1, 0.001), 1000);\n```\n\nWrite a\nfunction\n$R$ , $L$ , and\n$C$ of the circuit and the time increment\n$dt$.\n\nIn a manner similar to that of the\nfunction\nof exercise ,\nfunction\nthat takes the initial values of the state variables,\n$v_{C_{0}}$ and\n$i_{L_{0}}$ , and produces a pair\n(using pair)\nof the streams of states $v_{C}$ and\n$i_{L}$.\n\nUsing $R = 1$ ohm,\n$C= 0.2$ farad,\n$L = 1$ henry,\n$dt = 0.1$ second, and initial values\n$i_{L_{0}} = 0$ amps and\n$v_{C_{0}} = 10$ volts.", - "token_count": 280, + "content": "Now we can implement our function by delaying the evaluation of in the declaration of :\n\nIn general, every caller of must now\ndelay\nthe integrand argument.\n\nWe can demonstrate that the\nfunction\nworks by approximating\n$e\\approx 2.718$ by computing the value at\n$y=1$ of the solution to the differential\nequation $dy/dt=y$ with initial condition\n$y(0)=1$ :\n\n```javascript\nsolve_optimized\n\nfunction memo(fun) {\n let already_run = false;\n let result = undefined;\n return () => {\n if (!already_run) {\n result = fun();\n already_run = true;\n return result;\n } else {\n return result;\n }\n\t };\n}\n// note the use of the memoization optimization\nfunction stream_combine(f, s1, s2) {\n return is_null(s1) && is_null(s2)\n ? null\n : is_null(s1) || is_null(s2)\n ? error(null, \"unexpected argument -- stream_combine\")\n : pair(f(head(s1), head(s2)),\n memo(() => stream_combine(f, stream_tail(s1),\n stream_tail(s2))));\n}\nfunction add_streams(s1, s2) {\n return stream_combine((x1, x2) => x1 + x2, s1, s2);\n}\nfunction scale_stream(stream, factor) {\n return stream_map(x => x * factor,\n stream);\n}\n// note the use of the memoization optimization\nfunction integral(delayed_integrand, initial_value, dt) {\n const integ =\n pair(initial_value,\n memo(() => {\n const integrand = delayed_integrand();\n return add_streams(scale_stream(integrand, dt),\n integ);\n }));\n return integ;\n}\nfunction solve(f, y0, dt) {\n const y = integral(() => dy, y0, dt);\n const dy = stream_map(f, y);\n return y;\n}\n```\n\n```javascript\ne\n solve_optimized\n 2.716923932235896\n\nstream_ref(solve(y => y, 1, 0.001), 1000);\n\n2.716923932235896\n```\n\nWrite a\nfunction\nthat takes as arguments the parameters\n$R$ , $L$ , and\n$C$ of the circuit and the time increment\n$dt$.\n\nIn a manner similar to that of the\nfunction\nof exercise ,\nshould produce a\nfunction\nthat takes the initial values of the state variables,\n$v_{C_{0}}$ and\n$i_{L_{0}}$ , and produces a pair\n(using pair)\nof the streams of states $v_{C}$ and\n$i_{L}$.", + "token_count": 289, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3930,9 +5190,9 @@ "chunk_id": "Modularity_Objects_and_State_Streams_and_Delayed_Evaluation_3" }, { - "content": "Using $R = 1$ ohm,\n$C= 0.2$ farad,\n$L = 1$ henry,\n$dt = 0.1$ second, and initial values\n$i_{L_{0}} = 0$ amps and\n$v_{C_{0}} = 10$ volts.\n\nThe examples in this section illustrate how\ndelayed evaluation\nprovides great programming flexibility, but the same examples also show how\nthis can make our programs more complex.\n\nOur new\nfunction,\nfor instance, gives us the power to model systems with loops, but we must\nnow remember that\nfunction\nthat uses\nfunctions:\nordinary\nfunctions\nand\nfunctions\nthat take delayed arguments.\n\nIn general, creating separate classes of\nfunctions\nforces us to create separate classes of higher-order\nfunctions\nas well.\n\nOne way to avoid the need for two different classes of\nfunctions\nis to make all\nfunctions\ntake delayed arguments.\n\nWe could adopt a model of evaluation in which all\narguments to\nfunctions\nare automatically delayed and arguments are forced only when they are\nactually needed (for example, when they are required by a primitive\noperation).\n\nThis would transform our language to use normal-order\nevaluation, which we first described when we introduced the substitution\nmodel for evaluation in section.\n\nConverting to normal-order evaluation provides a uniform and elegant way to\nsimplify the use of delayed evaluation, and this would be a natural strategy\nto adopt if we were concerned only with stream processing.\n\nIn\nsection , after we have studied the\nevaluator, we will see how to transform our language in just this way.\n\nUnfortunately, including delays in\nfunction\ncalls wreaks havoc with our ability to design programs that depend on the\norder of events, such as programs that use assignment, mutate data, or\nperform input or output.\n\n```javascript\nEven a single delay in the tail of a pair can cause great confusion, as\n\tillustrated by exercises\n\tand.\n```", - "token_count": 294, - "has_code": true, + "content": "In a manner similar to that of the\nfunction\nof exercise ,\nshould produce a\nfunction\nthat takes the initial values of the state variables,\n$v_{C_{0}}$ and\n$i_{L_{0}}$ , and produces a pair\n(using pair)\nof the streams of states $v_{C}$ and\n$i_{L}$.\n\nThe examples in this section illustrate how\ndelayed evaluation\nprovides great programming flexibility, but the same examples also show how\nthis can make our programs more complex.\n\nOur new\nfunction,\nfor instance, gives us the power to model systems with loops, but we must\nnow remember that should be called\nwith a delayed integrand, and every\nfunction\nthat uses must be aware of this.\n\nIn effect, we have created two classes of\nfunctions:\nordinary\nfunctions\nand\nfunctions\nthat take delayed arguments.\n\nIn general, creating separate classes of\nfunctions\nforces us to create separate classes of higher-order\nfunctions\nas well.\n\nOne way to avoid the need for two different classes of\nfunctions\nis to make all\nfunctions\ntake delayed arguments.\n\nWe could adopt a model of evaluation in which all\narguments to\nfunctions\nare automatically delayed and arguments are forced only when they are\nactually needed (for example, when they are required by a primitive\noperation).\n\nThis would transform our language to use normal-order\nevaluation, which we first described when we introduced the substitution\nmodel for evaluation in section.\n\nConverting to normal-order evaluation provides a uniform and elegant way to\nsimplify the use of delayed evaluation, and this would be a natural strategy\nto adopt if we were concerned only with stream processing.\n\nIn\nsection , after we have studied the\nevaluator, we will see how to transform our language in just this way.", + "token_count": 276, + "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Streams", "subsection": "Streams and Delayed Evaluation", @@ -3940,8 +5200,8 @@ "chunk_id": "Modularity_Objects_and_State_Streams_and_Delayed_Evaluation_4" }, { - "content": "Unfortunately, including delays in\nfunction\ncalls wreaks havoc with our ability to design programs that depend on the\norder of events, such as programs that use assignment, mutate data, or\nperform input or output.\n\nAs far as anyone knows, mutability and delayed evaluation do not mix well\nin programming\nlanguages.\n\nA signal-flow diagram for the solution to a series RLC circuit.", - "token_count": 61, + "content": "In\nsection , after we have studied the\nevaluator, we will see how to transform our language in just this way.\n\nEven a single delay in the tail of a pair can cause great confusion, as illustrated by exercises and.\n\nAs far as anyone knows, mutability and delayed evaluation do not mix well\nin programming\nlanguages.\n\nA signal-flow diagram for the solution to a series RLC circuit.", + "token_count": 67, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "Streams", @@ -3950,9 +5210,9 @@ "chunk_id": "Modularity_Objects_and_State_Streams_and_Delayed_Evaluation_5" }, { - "content": "When we introduced compound functions in chapter , we used the ) to define what is meant by applying a function to arguments: - -\n\nTo apply a compound function to arguments, evaluate the return expression of the function (more generally, the body) with each parameter replaced by the corresponding\n\nargument.\n\nOnce we admit assignment into our programming language, such a\ndefinition is no longer adequate.\n\nIn particular,\nsection argued that, in the\npresence of assignment,\n\n```javascript\na name cannot be considered to be merely\n\trepresenting a value. Rather, a name must somehow designate a\n\tplace in which values can be stored.\n```\n\nIn our new model of evaluation, these places will be maintained in structures called environments.\n\nAn environment is a sequence of\nframes.\n\nEach frame is a table (possibly empty) of\nbindings , which associate\nnames\nwith their corresponding\nvalues.\n\n(A single frame may contain at most one binding for any name.)\nEach frame also has a pointer to its\nenclosing environment , unless, for the purposes of discussion, the\nframe is considered to be\nglobal.\n\nThe\nvalue of a name\nwith respect to an environment is the value given by the binding of\nthe\nname\nin the first frame in the environment that contains a\nbinding for that\nname.\n\nIf no frame in the sequence specifies a\nbinding for the\nname,\nthen the\nname\nis said to be\nunbound in the environment.\n\nA simple\n\nFigure\nshows a simple environment\nstructure consisting of three frames, labeled I, II, and III.\n\nIn the\ndiagram, A, B, C, and D are pointers to environments.\n\nC and D point\nto the same environment.\n\nThe\nnames\nII, while\nshadow the binding of\n\nThe environment is crucial to the evaluation process, because it determines\nthe context in which an expression should be evaluated.", + "content": "When we introduced compound functions in chapter , we used the substitution model of evaluation (section ) to define what is meant by applying a\n\nfunction to arguments: - - To apply a compound function to arguments, evaluate the return expression of the function (more generally, the body) with each\n\nparameter replaced by the corresponding argument.\n\nOnce we admit assignment into our programming language, such a\ndefinition is no longer adequate.\n\nIn particular,\nsection argued that, in the\npresence of assignment,\na name cannot be considered to be merely representing a value.\n\nRather, a name must somehow designate a place in which values can be stored.\n\nIn our new model of\nevaluation, these places will be maintained in structures called\nenvironments.\n\nAn environment is a sequence of\nframes.\n\nEach frame is a table (possibly empty) of\nbindings , which associate\nnames\nwith their corresponding\nvalues.\n\n(A single frame may contain at most one binding for any name.)\nEach frame also has a pointer to its\nenclosing environment , unless, for the purposes of discussion, the\nframe is considered to be\nglobal.\n\nThe\nvalue of a name\nwith respect to an environment is the value given by the binding of\nthe\nname\nin the first frame in the environment that contains a\nbinding for that\nname.\n\nIf no frame in the sequence specifies a\nbinding for the\nname,\nthen the\nname\nis said to be\nunbound in the environment.\n\nA simple environment structure.\n\nFigure\nshows a simple environment\nstructure consisting of three frames, labeled I, II, and III.\n\nIn the\ndiagram, A, B, C, and D are pointers to environments.\n\nC and D point\nto the same environment.\n\nThe\nnames\nand\nare bound in frame II, while\nand are bound\nin frame I.\n\nThe value of in environment D\nis 3.", "token_count": 300, - "has_code": true, + "has_code": false, "chapter": "Modularity, Objects, and State", "section": "The Environment Model of Evaluation", "subsection": null, @@ -3960,9 +5220,9 @@ "chunk_id": "Modularity_Objects_and_State_The_Environment_Model_of_Evaluation_1" }, { - "content": "The environment is crucial to the evaluation process, because it determines\nthe context in which an expression should be evaluated.\n\nIndeed, one could\nsay that expressions in a programming language do not, in themselves, have\nany meaning.\n\nRather, an expression acquires a meaning only with respect to\nsome environment in which it is evaluated.\n\n```javascript\nEven the interpretation of an expression as straightforward as\n\tdisplay(1) depends on an\n\tunderstanding that one is operating in a context in which the name\n```\n\nThus, in our model of evaluation we will always speak of evaluating an\nexpression with respect to some environment.\n\nTo describe interactions with\nthe interpreter, we will suppose that there is a\nnames\nassociated with the\nprimitive\nfunctions.\n\nFor example, the idea that\n\n```javascript\ndisplay is the name for the\n\tprimitive display function is captured by saying that the\n\tname display\n```\n\nis bound in the global environment to the primitive display function.\n\nBefore we evaluate a program, we extend the global\nenvironment with a new frame, the\nprogram frame , resulting in the\nprogram environment.\n\nWe will add\nthe names that are declared at the top level of the\nprogram, outside of any block, to this frame.\n\nThe given program\nis then evaluated with respect to the program environment.", - "token_count": 211, - "has_code": true, + "content": "The value of in environment D\nis 3.\n\nThis is determined as follows: We examine the\nfirst frame in the sequence (frame III) and do not find a binding for\n, so we proceed to the enclosing environment\nD and find the binding in frame I.\n\nOn the other hand, the value of\nin environment A is 7, because the first\nframe in the sequence (frame II) contains a binding of\nto 7.\n\nWith respect to environment A, the\nbinding of to 7 in frame II is said to\nshadow the binding of to 3 in\nframe I.\n\nThe environment is crucial to the evaluation process, because it determines\nthe context in which an expression should be evaluated.\n\nIndeed, one could\nsay that expressions in a programming language do not, in themselves, have\nany meaning.\n\nRather, an expression acquires a meaning only with respect to\nsome environment in which it is evaluated.\n\nEven the interpretation of an expression as straightforward as display(1) depends on an understanding that one is operating in a context in which the name refers to the primitive function that displays a value.\n\nThus, in our model of evaluation we will always speak of evaluating an\nexpression with respect to some environment.\n\nTo describe interactions with\nthe interpreter, we will suppose that there is a\nglobal environment, consisting of a single frame (with no enclosing\nenvironment) that includes values for the\nnames\nassociated with the\nprimitive\nfunctions.\n\nFor example, the idea that\ndisplay is the name for the primitive display function is captured by saying that the name display\nis bound in the global environment to the primitive\ndisplay function.\n\nBefore we evaluate a program, we extend the global\nenvironment with a new frame, the\nprogram frame , resulting in the\nprogram environment.", + "token_count": 296, + "has_code": false, "chapter": "Modularity, Objects, and State", "section": "The Environment Model of Evaluation", "subsection": null, @@ -3970,8 +5230,18 @@ "chunk_id": "Modularity_Objects_and_State_The_Environment_Model_of_Evaluation_2" }, { - "content": "we showed how the\n\n```javascript\napplication\n f(5)\n```\n\nevaluates to 136, given the following function declarations:\n\n```javascript\nf_example\n 136\n\nfunction square(x) {\n return x * x;\n}\nfunction sum_of_squares(x, y) {\n return square(x) + square(y);\n}\nfunction f(a) {\n return sum_of_squares(a + 1, a * 2);\n}\n```\n\nWe can analyze the same example using the environment model.\n\nFigure shows the three\nfunction\nobjects created by evaluating the definitions of\nsum_of_squares\nin the\nprogram\nenvironment.\n\nEach\nfunction\nobject consists of some code, together with a pointer to the\nprogram\nenvironment.\n\nFunction objects in the program frame.\n\nfigure\nwe see the environment structure created by evaluating the expression\nf(5).\n\nThe call to parameter of\n\n```javascript\nreturn sum_of_squares(a + 1, a * 2);\n```\n\nTo evaluate\n\n```javascript\nthe return statement, we first evaluate the\n\tsubexpressions of the return expression.\n```\n\nThe first subexpression,\nsum_of_squares,\nhas a value that is a\nfunction\nobject.\n\n(Notice how this value is found: We first look in the first frame\nof E1, which contains no binding for\nsum_of_squares.\n\nThen we proceed to the enclosing environment, i.e., the\nprogram\nenvironment, and find the binding shown in\nfigure.)\nThe other two subexpressions are evaluated by applying the primitive\noperations\na + 1\nand\na * 2\nto obtain 6 and 10, respectively.\n\nNow we apply the\nfunction\nobject\nsum_of_squares\nto the arguments 6 and 10.\n\nThis results in a new environment, E2, in which\nthe parameters\n\n```javascript\nthe statement\n\nreturn square(x) + square(y);\n```\n\nThis leads us to evaluate square(x), where program frame and\n\n```javascript\nreturn x * x;.\n```\n\nAlso as part of applying sum_of_squares, we must evaluate the subexpression square(y), where parameter of\n\n```javascript\nreturn x * x;.\n```\n\nThe important point to observe is that each call to program environment, since this is the environment indicated by the function object.\n\nAfter the subexpressions are evaluated, the results are returned.", - "token_count": 313, + "content": "Before we evaluate a program, we extend the global\nenvironment with a new frame, the\nprogram frame , resulting in the\nprogram environment.\n\nThe given program\nis then evaluated with respect to the program environment.", + "token_count": 35, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": null, + "chunk_index": 3, + "chunk_id": "Modularity_Objects_and_State_The_Environment_Model_of_Evaluation_3" + }, + { + "content": "When we introduced the substitution model in section we showed how the application f(5) evaluates to 136, given the following function declarations:\n\n```javascript\nf_example\n 136\n\nfunction square(x) {\n return x * x;\n}\nfunction sum_of_squares(x, y) {\n return square(x) + square(y);\n}\nfunction f(a) {\n return sum_of_squares(a + 1, a * 2);\n}\n```\n\nWe can analyze the same example using the environment model.\n\nFigure shows the three\nfunction\nobjects created by evaluating the definitions of\n, , and\nsum_of_squares\nin the\nprogram\nenvironment.\n\nEach\nfunction\nobject consists of some code, together with a pointer to the\nprogram\nenvironment.\n\nFunction objects in the program frame.\n\nIn\nfigure\nwe see the environment structure created by evaluating the expression\nf(5).\n\nThe call to creates a new environment, E1,\nbeginning with a frame in which , the\nparameter of\n, is bound to the argument 5.\n\nIn E1, we\nevaluate the body of :\n\n```javascript\nreturn sum_of_squares(a + 1, a * 2);\n```\n\nTo evaluate\nthe return statement, we first evaluate the subexpressions of the return expression.\n\nThe first subexpression,\nsum_of_squares,\nhas a value that is a\nfunction\nobject.\n\n(Notice how this value is found: We first look in the first frame\nof E1, which contains no binding for\nsum_of_squares.\n\nThen we proceed to the enclosing environment, i.e., the\nprogram\nenvironment, and find the binding shown in\nfigure.)\nThe other two subexpressions are evaluated by applying the primitive\noperations and\nto evaluate the two combinations\na + 1\nand\na * 2\nto obtain 6 and 10, respectively.\n\nNow we apply the\nfunction\nobject\nsum_of_squares\nto the arguments 6 and 10.\n\nThis results in a new environment, E2, in which\nthe parameters\nand are bound\nto the arguments.", + "token_count": 285, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "The Environment Model of Evaluation", @@ -3980,8 +5250,8 @@ "chunk_id": "Modularity_Objects_and_State_Applying_Simple_Functions_1" }, { - "content": "After the subexpressions are evaluated, the results are returned.\n\nThe\nvalues generated by the two calls to\nsum_of_squares,\nand this result is returned by.", - "token_count": 24, + "content": "This results in a new environment, E2, in which\nthe parameters\nand are bound\nto the arguments.\n\nOnce again, we set up a\nnew environment, E3, in which is bound to 6,\nand within this we evaluate the body of ,\nwhich is\nreturn x * x;.\n\nAlso as part of applying\nsum_of_squares,\nwe must evaluate the subexpression\nsquare(y),\nwhere is 10.\n\nThis second call to\ncreates another environment, E4, in\nwhich , the\nparameter of\n, is bound to 10.\n\nAnd within E4 we must\nevaluate\nreturn x * x;.\n\nThe important point to observe is that each call to\ncreates a new environment containing a\nbinding for.\n\nWe can see here how the\ndifferent frames serve to keep separate the different local variables all\nnamed.\n\nNotice that each frame created by\npoints to the\nprogram\nenvironment, since this is the environment indicated by the\nfunction\nobject.\n\nAfter the subexpressions are evaluated, the results are returned.\n\nThe\nvalues generated by the two calls to are\nadded by\nsum_of_squares,\nand this result is returned by.\n\nSince our focus here is on the environment structures, we will not\ndwell on how these returned values are passed from call to call;\nhowever, this is also an important aspect of the evaluation process,\nand we will return to it in detail in chapter.", + "token_count": 220, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "The Environment Model of Evaluation", @@ -4000,8 +5270,8 @@ "chunk_id": "Modularity_Objects_and_State_CSE_Machine_1" }, { - "content": "We can turn to the environment model to see how\nfunctions\nand assignment can be used to represent objects with local state.\n\nAs an\nexample, consider the\nwithdrawal processor from\nsection created by calling the\nfunction\n\n```javascript\nmake_withdraw2\n\nfunction make_withdraw(balance) {\n return amount => {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"insufficient funds\";\n }\n };\n}\n```\n\nLet us describe the evaluation of\n\n```javascript\nmake_withdraw2\n make_withdraw2_w1_declare\n\nconst W1 = make_withdraw(100);\n```\n\nfollowed by\n\n```javascript\nmake_withdraw2_w1_example\n make_withdraw2_w1_declare\n 50\n\nW1(50);\n```\n\nFigure shows the result of\n\n```javascript\ndeclaring the\n make_withdraw\n```\n\nfunction\nin the\nprogram\nenvironment.\n\nThis produces a\nfunction\nobject that contains a pointer to the\nprogram\nenvironment.\n\nSo far, this is no different from the examples we have already\nseen, except that\n\n```javascript\nthe return expression in the body of the function is itself\n\ta lambda expression.\n```\n\n```javascript\nResult of defining\n\t make_withdraw in\n\t the program environment.\n```\n\nThe interesting part of the computation happens when we apply the function make_withdraw to an argument:\n\n```javascript\nconst W1 = make_withdraw(100);\n```\n\nWe begin, as usual, by setting up an environment E1 in which the parameter make_withdraw, namely the\n\n```javascript\nreturn statement whose return expression is\n a lambda expression. The evaluation of this lambda expression\n```\n\nconstructs a new\nfunction\nobject, whose code is as specified by the\nlambda expression\nand whose environment is E1, the environment in which the\nlambda expression\nwas evaluated to produce the\nfunction.\n\nThe resulting\nfunction\nobject is the value returned by the call to\nmake_withdraw.\n\nThis is bound to\nprogram\nenvironment, since the\nconstant declaration\nitself is being evaluated in the\nprogram\nenvironment.\n\nFigure\nshows the resulting environment structure.\n\n```javascript\nResult of evaluating\n\t const W1 = make_withdraw(100);.\n```\n\nNow we can analyze what happens when\n\n```javascript\nW1(50);\n```", - "token_count": 302, + "content": "We can turn to the environment model to see how\nfunctions\nand assignment can be used to represent objects with local state.\n\nAs an\nexample, consider the\nwithdrawal processor from\nsection created by calling the\nfunction\n\n```javascript\nmake_withdraw2\n\nfunction make_withdraw(balance) {\n return amount => {\n if (balance >= amount) {\n balance = balance - amount;\n return balance;\n } else {\n return \"insufficient funds\";\n }\n };\n}\n```\n\nLet us describe the evaluation of\n\n```javascript\nmake_withdraw2\n make_withdraw2_w1_declare\n\nconst W1 = make_withdraw(100);\n```\n\nfollowed by\n\n```javascript\nmake_withdraw2_w1_example\n make_withdraw2_w1_declare\n 50\n\nW1(50);\n\n50\n```\n\nFigure\nshows the result of\ndeclaring the make_withdraw\nfunction\nin the\nprogram\nenvironment.\n\nThis produces a\nfunction\nobject that contains a pointer to the\nprogram\nenvironment.\n\nSo far, this is no different from the examples we have already\nseen, except that\nthe return expression in the body of the function is itself a lambda expression.\n\nResult of defining make_withdraw in the program environment.\n\nThe interesting part of the computation happens when we apply the function make_withdraw to an argument:\n\n```javascript\nconst W1 = make_withdraw(100);\n```\n\nWe begin, as usual, by setting up an environment E1 in which the\nparameter\nis bound to the argument 100.\n\nWithin\nthis environment, we evaluate the body of\nmake_withdraw,\nnamely the\nreturn statement whose return expression is a lambda expression.\n\nThe evaluation of this lambda expression\nconstructs a new\nfunction\nobject, whose code is as specified by the\nlambda expression\nand whose environment is E1, the environment in which the\nlambda expression\nwas evaluated to produce the\nfunction.\n\nThe resulting\nfunction\nobject is the value returned by the call to\nmake_withdraw.\n\nThis is bound to in the\nprogram\nenvironment, since the\nconstant declaration\nitself is being evaluated in the\nprogram\nenvironment.\n\nFigure\nshows the resulting environment structure.\n\nResult of evaluating const W1 = make_withdraw(100);.", + "token_count": 299, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "The Environment Model of Evaluation", @@ -4010,8 +5280,8 @@ "chunk_id": "Modularity_Objects_and_State_Frames_as_the_Repository_of_Local_State_1" }, { - "content": "Now we can analyze what happens when\n\nWe begin by constructing a frame in which\nparameter of\nprogram\nenvironment, but rather the environment E1, because this is the\nenvironment that is specified by the\nfunction\nobject.\n\nWithin this new environment, we evaluate the body of the\nfunction:\n\n```javascript\nif (balance >= amount) {\n balance = balance - amount;\n return balance;\n} else {\n return \"insufficient funds\";\n}\n```\n\nThe resulting environment structure is shown in\nfigure.\n\nThe expression being evaluated references\nboth\nThe variable amount\nwill be found in the first frame in the environment, and\n\n```javascript\nEnvironments created by applying the function object\n\t W1.\n```\n\nWhen the\nassignment\nis executed, the binding of\nfunction\nobject\nfunction\ncall that constructed it has terminated, and there are no pointers to that\nframe from other parts of the environment.\n\nThe next time\nplace that holds the local\nstate variable for the\nfunction\nobject\nFigure\nshows the situation after the call to\n\n```javascript\nEnvironments after the call to\n\t W1.\n```\n\nObserve what happens when we create a second withdraw object by making another call to make_withdraw:\n\n```javascript\nmake_withdraw2\n make_withdraw2_w2_declare\n 20\n\nconst W2 = make_withdraw(100);\n\nconst W1 = make_withdraw(100);\nW1(50);\nconst W2 = make_withdraw(100);\nW2(80);\n```\n\nThis produces the environment structure of\nfigure,\nwhich shows\nthat\nfunction\nobject, that is, a pair with some code and an environment.\n\nThe environment\nE2 for\nmake_withdraw.\n\nIt contains a frame with its own local binding for\nlambda\nexpression in the body of\nmake_withdraw.\n\n```javascript\nUsing\n\t const W2 = make_withdraw(100);\n\t to create a second object.\n```", - "token_count": 258, + "content": "Result of evaluating const W1 = make_withdraw(100);.\n\n```javascript\nW1(50);\n\n50\n```\n\nWe begin by constructing a frame in which\n, the\nparameter of\n, is bound to the argument 50.\n\nThe crucial\npoint to observe is that this frame has as its enclosing environment not the\nprogram\nenvironment, but rather the environment E1, because this is the\nenvironment that is specified by the\nfunction\nobject.\n\nWithin this new environment, we evaluate the body of the\nfunction:\n\n```javascript\nif (balance >= amount) {\n balance = balance - amount;\n return balance;\n} else {\n return \"insufficient funds\";\n}\n```\n\nThe resulting environment structure is shown in\nfigure.\n\nThe expression being evaluated references\nboth and.\n\nThe variable amount\nwill be found in the first frame in the environment, and\nwill be found by following the\nenclosing-environment pointer to E1.\n\nEnvironments created by applying the function object W1.\n\nWhen the\nassignment\nis executed, the binding of in E1 is\nchanged.\n\nAt the completion of the call to\n, is 50,\nand the frame that contains is still\npointed to by the\nfunction\nobject.\n\nThe frame that binds\n(in which we executed the code that\nchanged ) is no longer relevant, since\nthe\nfunction\ncall that constructed it has terminated, and there are no pointers to that\nframe from other parts of the environment.\n\nThe next time\nis called, this will build a new frame that\nbinds and whose enclosing environment is\nE1.\n\nWe see that E1 serves as the place that holds the local\nstate variable for the\nfunction\nobject.\n\nFigure\nshows the situation after the call to.\n\nEnvironments after the call to W1.\n\nObserve what happens when we create a second withdraw object by making another call to make_withdraw:\n\n```javascript\nmake_withdraw2\n make_withdraw2_w2_declare\n 20\n\nconst W2 = make_withdraw(100);\n\nconst W1 = make_withdraw(100);\nW1(50);\nconst W2 = make_withdraw(100);\nW2(80);\n```", + "token_count": 305, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "The Environment Model of Evaluation", @@ -4020,8 +5290,18 @@ "chunk_id": "Modularity_Objects_and_State_Frames_as_the_Repository_of_Local_State_2" }, { - "content": "In this section we handle the evaluation of function bodies or other\nblocks (such as the branches of conditional statements) that contain\ndeclarations.\n\nEach block opens a new scope for names declared in the block.\n\nIn order to evaluate a block in a given environment, we extend that\nenvironment by a new frame that contains all names declared directly\n(that is, outside of nested blocks) in the body of the block and\nthen evaluate the body in the newly constructed environment.\n\nSection introduced the idea that functions can have internal declarations, thus leading to a block structure as in the function to compute square roots:\n\n```javascript\nanother_sqrt\n abs_definition\n square_definition\n average_definition\n sqrt_example7\n 2.2360688956433634\n\nfunction sqrt(x) {\n function is_good_enough(guess) {\n return abs(square(guess) - x) < 0.001;\n }\n function improve(guess) {\n return average(guess, x / guess);\n }\n function sqrt_iter(guess){\n return is_good_enough(guess)\n ? guess\n : sqrt_iter(improve(guess));\n }\n return sqrt_iter(1);\n}\n```\n\nNow we can use the environment model to see why these internal\ndeclarations\nbehave as desired.\n\nFigure\nshows the point in the evaluation of the expression\nsqrt(2)\nwhere the internal\nfunction\nis_good_enough\nhas been called for the first time with\n1.\n\nThe\n\nObserve the structure of the environment.\n\nThe name\nto a\nfunction\nobject whose associated environment is the\nprogram\nenvironment.\n\nWhen\nprogram\nenvironment, in which the parameter 2.\n\nThe body of E1.\n\n```javascript\nThat body is a block with local\n\tfunction declarations and therefore E1 was extended with a new frame for\n\tthose declarations, resulting in the new environment E2. The body\n\tof the block was then evaluated in E2. Since the first statement\n\tin the body is\n```\n\n```javascript\nabs_definition\n square_definition\n\nfunction is_good_enough(guess) {\n return abs(square(guess) - x) < 0.001;\n}\n```\n\n```javascript\nevaluating this declaration created the function\n\tis_good_enough\n\tin the environmentE2.\n```", - "token_count": 293, + "content": "Observe what happens when we create a second withdraw object by making another call to make_withdraw:\n\nThe environment\nE2 for was created by the call to\nmake_withdraw.\n\nIt contains a frame with its own local binding for.\n\nOn the other hand,\nand have the\nsame code: the code specified by the\nlambda\nexpression in the body of\nmake_withdraw.\n\nWe see\nhere why and\nbehave as independent objects.\n\nCalls to\nreference the state variable\nstored in E1, whereas calls to\nreference the\nstored in E2.\n\nThus, changes to the\nlocal state of one object do not affect the other object.\n\nUsing const W2 = make_withdraw(100); to create a second object.", + "token_count": 109, + "has_code": false, + "chapter": "Modularity, Objects, and State", + "section": "The Environment Model of Evaluation", + "subsection": "Frames as the Repository of Local State", + "chunk_index": 3, + "chunk_id": "Modularity_Objects_and_State_Frames_as_the_Repository_of_Local_State_3" + }, + { + "content": "In this section we handle the evaluation of function bodies or other\nblocks (such as the branches of conditional statements) that contain\ndeclarations.\n\nEach block opens a new scope for names declared in the block.\n\nIn order to evaluate a block in a given environment, we extend that\nenvironment by a new frame that contains all names declared directly\n(that is, outside of nested blocks) in the body of the block and\nthen evaluate the body in the newly constructed environment.\n\nSection introduced the idea that functions can have internal declarations, thus leading to a block structure as in the following function to compute square roots:\n\n```javascript\nanother_sqrt\n abs_definition\n square_definition\n average_definition\n sqrt_example7\n 2.2360688956433634\n\nfunction sqrt(x) {\n function is_good_enough(guess) {\n return abs(square(guess) - x) < 0.001;\n }\n function improve(guess) {\n return average(guess, x / guess);\n }\n function sqrt_iter(guess){\n return is_good_enough(guess)\n ? guess\n : sqrt_iter(improve(guess));\n }\n return sqrt_iter(1);\n}\n```\n\nNow we can use the environment model to see why these internal\ndeclarations\nbehave as desired.\n\nFigure\nshows the point in the evaluation of the expression\nsqrt(2)\nwhere the internal\nfunction\nis_good_enough\nhas been called for the first time with\nequal to 1.\n\nThe function with internal declarations.\n\nObserve the structure of the environment.\n\nThe name is bound in the program environment\nto a\nfunction\nobject whose associated environment is the\nprogram\nenvironment.\n\nWhen was called, a new\nenvironment, E1, was formed, subordinate to the\nprogram\nenvironment, in which the parameter is bound\nto 2.\n\nThe body of was then\nevaluated in E1.\n\nThat body is a block with local function declarations and therefore E1 was extended with a new frame for those declarations, resulting in the new environment E2.\n\nThe body of the block was then evaluated in E2.\n\nSince the first statement in the body is\n\n```javascript\nabs_definition\n square_definition\n\nfunction is_good_enough(guess) {\n return abs(square(guess) - x) < 0.001;\n}\n```", + "token_count": 310, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "The Environment Model of Evaluation", @@ -4030,9 +5310,9 @@ "chunk_id": "Modularity_Objects_and_State_Internal_Declarations_1" }, { - "content": "The body of E1.\n\n```javascript\nTobe more precise,\n\tthe name is_good_enough\n\tin the first frame of E2 was bound to a function\n\tobject whose associated environment is E2.\n```\n\nSimilarly,\nsqrt_iter\nwere defined as\nfunctions in E2.\n\nFor conciseness,\nfigure\nshows only the\nfunction\nobject for\nis_good_enough.\n\nAfter the local\nfunctions\nwere defined, the expression\nsqrt_@iter(1)\nwas evaluated, still in environment\nE2.\n\nSo the\nfunction\nobject bound to\n\n```javascript\nsqrt_@iter\n in E2 was called with 1 as an argument. This created an environment E3\n in which\n```\n\nsqrt_@iter,\nis bound to 1.\n\nThe function sqrt_@iter\nin turn called\nis_@good_@enough\nwith the value of\n\n```javascript\n(from E3) as the argument for\n\tis_@good_@enough.\n```\n\nThis set up another environment,\n\n```javascript\nE4, in which\n\tis_@good_@enough)\n```\n\nis bound to 1.\n\nAlthough\nsqrt_@iter\nand\nis_@good_@enough\nboth have a parameter named\n\n```javascript\nAlso, E3 and E4 both have E2 as their enclosing environment, because the\n\tsqrt_@iter\n\tand\n\tis_@good_@enough functions\n\tboth have E2 as their environment part.\n```\n\nOne consequence of this is that the name is_@good_@enough will reference the binding of function was called.\n\nThe environment model thus explains the two key properties that make local\nfunction declarations\na useful technique for modularizing programs:\n-\n-\nThe names of the local\nfunctions\ndo not interfere with\nnames external to the enclosing\nfunction,\nbecause the local\nfunction\nnames will be bound in the frame that the\nblock creates when it is evaluated,\nrather than being bound in the\nprogram\nenvironment.\n-\n-\nThe local\nfunctions\ncan access the arguments of the enclosing\nfunction,\nsimply by using parameter names as free\nnames.\n\nThis is\nbecause the body of the local\nfunction\nis evaluated in an environment that is subordinate to the\nevaluation environment for the enclosing\nfunction.\n\nAs we saw, the scope of the names declared in\nsqrt is the whole body of\nsqrt.", - "token_count": 307, - "has_code": true, + "content": "Since the first statement in the body is\n\nTobe more precise, the name is_good_enough in the first frame of E2 was bound to a function object whose associated environment is E2.\n\nSimilarly,\nand\nsqrt_iter\nwere defined as\nfunctions in E2.\n\nFor conciseness,\nfigure\nshows only the\nfunction\nobject for\nis_good_enough.\n\nAfter the local\nfunctions\nwere defined, the expression\nsqrt_@iter(1)\nwas evaluated, still in environment\nE2.\n\nSo the\nfunction\nobject bound to\nsqrt_@iter in E2 was called with 1 as an argument.\n\nThis created an environment E3 in which\n, the parameter of\nsqrt_@iter,\nis bound to 1.\n\nThe function sqrt_@iter\nin turn called\nis_@good_@enough\nwith the value of\n(from E3) as the argument for is_@good_@enough.\n\nThis set up another environment,\nE4, in which (the parameter of is_@good_@enough)\nis bound to 1.\n\nAlthough\nsqrt_@iter\nand\nis_@good_@enough\nboth have a parameter named , these are two\ndistinct local variables located in different frames.\n\nAlso, E3 and E4 both have E2 as their enclosing environment, because the sqrt_@iter and is_@good_@enough functions both have E2 as their environment part.\n\nOne consequence of this is that the\nname\nthat appears in the body of\nis_@good_@enough\nwill reference the binding of that appears in\nE1, namely the value of with which the\noriginal\nfunction\nwas called.\n\nThe environment model thus explains the two key properties that make local\nfunction declarations\na useful technique for modularizing programs:\n-\n-\nThe names of the local\nfunctions\ndo not interfere with\nnames external to the enclosing\nfunction,\nbecause the local\nfunction\nnames will be bound in the frame that the\nblock creates when it is evaluated,\nrather than being bound in the\nprogram\nenvironment.\n-\n-\nThe local\nfunctions\ncan access the arguments of the enclosing\nfunction,\nsimply by using parameter names as free\nnames.", + "token_count": 296, + "has_code": false, "chapter": "Modularity, Objects, and State", "section": "The Environment Model of Evaluation", "subsection": "Internal Declarations", @@ -4040,8 +5320,8 @@ "chunk_id": "Modularity_Objects_and_State_Internal_Declarations_2" }, { - "content": "As we saw, the scope of the names declared in\nsqrt is the whole body of\nsqrt.\n\nThis explains why\nmutual recursion works, as in this (quite\nwasteful) way of checking whether a nonnegative\ninteger is even.\n\n```javascript\nf_is_even_is_odd\n\nfunction f(x) {\n function is_even(n) {\n return n === 0\n ? true\n : is_odd(n - 1);\n }\n function is_odd(n) {\n return n === 0\n ? false\n : is_even(n - 1);\n }\n return is_even(x);\n}\n```\n\nAt the time when\nis_even is called during a call to\nf , the environment diagram looks\nlike the one in figure when\nsqrt_iter is called.\n\nThe functions\nis_even and\nis_odd are bound in E2 to function objects\nthat point to E2 as the environment in which to evaluate calls to those\nfunctions.\n\nThus\nis_odd in the body of\nis_even refers to the right function.\n\nAlthough\nis_odd\nis defined after\nis_even ,\nthis is no different from how in the body of\nsqrt_iter\nthe name\nimprove\nand the name\nsqrt_iter\nitself refer to the right functions.\n\nEquipped with a way to handle declarations within blocks, we can\nrevisit declarations of names at the top level.\n\nIn\nsection , we saw\nthat the names declared at the top level are added to the program\nframe.\n\nA better explanation is that the whole program is placed in\nan implicit block, which is evaluated in the global environment.\n\nThe treatment of blocks described above then handles the top\nlevel:\nThe global environment is extended by a frame that contains the\nbindings of all names declared in the implicit block.\n\nThat frame is\nthe program frame and the resulting\nenvironment is the\n\nWe said that a block s body is evaluated in an environment that\ncontains all names declared directly in the body of the block.", - "token_count": 296, + "content": "The environment model thus explains the two key properties that make local\nfunction declarations\na useful technique for modularizing programs:\n-\n-\nThe names of the local\nfunctions\ndo not interfere with\nnames external to the enclosing\nfunction,\nbecause the local\nfunction\nnames will be bound in the frame that the\nblock creates when it is evaluated,\nrather than being bound in the\nprogram\nenvironment.\n-\n-\nThe local\nfunctions\ncan access the arguments of the enclosing\nfunction,\nsimply by using parameter names as free\nnames.\n\nAs we saw, the scope of the names declared in\nsqrt is the whole body of\nsqrt.\n\nThis explains why\nmutual recursion works, as in this (quite\nwasteful) way of checking whether a nonnegative\ninteger is even.\n\n```javascript\nf_is_even_is_odd\n\nfunction f(x) {\n function is_even(n) {\n return n === 0\n ? true\n : is_odd(n - 1);\n }\n function is_odd(n) {\n return n === 0\n ? false\n : is_even(n - 1);\n }\n return is_even(x);\n}\n```\n\nAt the time when\nis_even is called during a call to\nf , the environment diagram looks\nlike the one in figure when\nsqrt_iter is called.\n\nThe functions\nis_even and\nis_odd are bound in E2 to function objects\nthat point to E2 as the environment in which to evaluate calls to those\nfunctions.\n\nThus\nis_odd in the body of\nis_even refers to the right function.\n\nAlthough\nis_odd\nis defined after\nis_even ,\nthis is no different from how in the body of\nsqrt_iter\nthe name\nimprove\nand the name\nsqrt_iter\nitself refer to the right functions.\n\nEquipped with a way to handle declarations within blocks, we can\nrevisit declarations of names at the top level.\n\nIn\nsection , we saw\nthat the names declared at the top level are added to the program\nframe.", + "token_count": 294, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "The Environment Model of Evaluation", @@ -4050,8 +5330,8 @@ "chunk_id": "Modularity_Objects_and_State_Internal_Declarations_3" }, { - "content": "We said that a block s body is evaluated in an environment that\ncontains all names declared directly in the body of the block.\n\nA locally declared name is put into the environment when the block is\nentered, but without an associated value.\n\nThe evaluation of its\ndeclaration during evaluation of the block body then assigns to the\nname the result of evaluating the expression to the right of the\n= , as if the declaration were\nan assignment.\n\nSince the addition of the name to the environment is\nseparate from the evaluation of the declaration, and the whole block\nis in the scope of the name, an erroneous program could attempt to", - "token_count": 113, + "content": "In\nsection , we saw\nthat the names declared at the top level are added to the program\nframe.\n\nThe treatment of blocks described above then handles the top\nlevel:\nThe global environment is extended by a frame that contains the\nbindings of all names declared in the implicit block.\n\nThat frame is\nthe program frame and the resulting\nenvironment is the\nprogram environment.\n\nWe said that a block s body is evaluated in an environment that\ncontains all names declared directly in the body of the block.\n\nA locally declared name is put into the environment when the block is\nentered, but without an associated value.\n\nThe evaluation of its\ndeclaration during evaluation of the block body then assigns to the\nname the result of evaluating the expression to the right of the\n= , as if the declaration were\nan assignment.\n\nSince the addition of the name to the environment is\nseparate from the evaluation of the declaration, and the whole block\nis in the scope of the name, an erroneous program could attempt to\naccess the value of a name before its declaration is evaluated;\nthe evaluation of an unassigned name signals an error.", + "token_count": 197, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "The Environment Model of Evaluation", @@ -4060,8 +5340,8 @@ "chunk_id": "Modularity_Objects_and_State_Internal_Declarations_4" }, { - "content": "The overall specification of how the interpreter\nfunction application\nremains the same as when we first introduced it in\nsection :\n-\n-\nTo evaluate\nan application:\n-\n-\nEvaluate the subexpressions\nof the\napplication.\n-\n-\nApply the value of the\nfunction\nsubexpression\nto the values of the\nargument\nsubexpressions.\n\nThe environment model of evaluation replaces the substitution model in\nspecifying what it means to apply a compound\nfunction\nto arguments.\n\nIn the environment model of evaluation, a\nfunction\nis always a pair consisting of some code and a pointer to an environment.\n\nFunctions\nare created in one way only: by evaluating a\nlambda\nexpression.\nfunction\nwhose code is obtained from the text of the\nlambda\nexpression and whose environment is the environment in which the\nlambda\nexpression was evaluated to produce the\nfunction.\n\nFor example, consider the\nfunction declaration\n\n```javascript\nsquare_example\n 196\n\nfunction square(x) {\n return x * x;\n}\n```\n\nevaluated in the\nprogram\nenvironment.\n\nThe\nfunction declaration\nsyntax is\nequivalent to\nan underlying implicit\nlambda\nexpression.\n\nIt would have been equivalent to have used\n\n```javascript\nsquare_example\n 196\n\nconst square = x => x * x;\n```\n\nwhich evaluates\n\n```javascript\nx => x * x\n```\n\nand binds program environment.\n\nFigure shows the result of evaluating this declaration statement.\n\n```javascript\nThe global environment encloses the program environment. To reduce\n\tclutter, after this figure we will not display the global environment\n\t(as it is always the same), but we are reminded of its existence by the\n\tpointer from the program environment upward.\n```\n\nThe function object is a pair whose code specifies that the function has one parameter, namely function body\n\n```javascript\nreturn x * x;.\n```\n\nThe environment part of the\nfunction\nis a pointer to the program environment, since that is the environment in\nwhich the\nlambda\nexpression was evaluated to produce the\nfunction.", - "token_count": 308, + "content": "The overall specification of how the interpreter\nevaluates a\nfunction application\nremains the same as when we first introduced it in\nsection :\n-\n-\nTo evaluate\nan application:\n-\n-\nEvaluate the subexpressions\nof the\napplication.\n-\n-\nApply the value of the\nfunction\nsubexpression\nto the values of the\nargument\nsubexpressions.\n\nThe environment model of evaluation replaces the substitution model in\nspecifying what it means to apply a compound\nfunction\nto arguments.\n\nIn the environment model of evaluation, a\nfunction\nis always a pair consisting of some code and a pointer to an environment.\n\nFunctions\nare created in one way only: by evaluating a\nlambda\nexpression.\n\nThis produces a\nfunction\nwhose code is obtained from the text of the\nlambda\nexpression and whose environment is the environment in which the\nlambda\nexpression was evaluated to produce the\nfunction.\n\nFor example, consider the\nfunction declaration\n\n```javascript\nsquare_example\n 196\n\nfunction square(x) {\n return x * x;\n}\n```\n\nevaluated in the\nprogram\nenvironment.\n\nThe\nfunction declaration\nsyntax is\nequivalent to\nan underlying implicit\nlambda\nexpression.\n\nIt would have been equivalent to have used\n\n```javascript\nsquare_example\n 196\n\nconst square = x => x * x;\n```\n\nwhich evaluates x => x * x and binds to the resulting value, all in the program environment.\n\nFigure\nshows the result of evaluating this\ndeclaration statement.\n\nThe global environment encloses the program environment.\n\nTo reduce clutter, after this figure we will not display the global environment (as it is always the same), but we are reminded of its existence by the pointer from the program environment upward.\n\nThe\nfunction\nobject is a pair whose code specifies that the\nfunction\nhas one\nparameter, namely , and a\nfunction\nbody\nreturn x * x;.", + "token_count": 288, "has_code": true, "chapter": "Modularity, Objects, and State", "section": "The Environment Model of Evaluation", @@ -4070,9 +5350,9 @@ "chunk_id": "Modularity_Objects_and_State_The_Rules_for_Evaluation_1" }, { - "content": "The environment part of the\nfunction\nis a pointer to the program environment, since that is the environment in\nwhich the\nlambda\nexpression was evaluated to produce the\nfunction.\n\nA new binding, which associates the\nfunction\nobject with the\nname\n\n```javascript\nEnvironment structure produced by evaluating\n\t function square(x) { return x * x; }\n\t in the program environment.\n```\n\nIn general, const ,\nfunction , and\nlet\nadd bindings to frames.\n\nAssignment is forbidden on constants, so our environment model\nneeds to distinguish names that refer to constants\nfrom names that refer to variables.\n\nWe indicate that\na name is a constant by writing an equal sign after the colon\nthat follows the name.\n\nWe consider function declarations as equivalent to constant\ndeclarations;.\n\nNow that we have seen how\nfunctions\nare created, we can describe how\nfunctions\nare applied.\n\nThe environment model specifies: To apply a\nfunction\nto arguments, create a new environment containing a frame that binds the\nparameters to the values of the arguments.\n\nThe enclosing environment of\nthis frame is the environment specified by the\nfunction.\n\nNow, within this new environment, evaluate the\nfunction\nbody.\n\nTo show how this rule is followed,\nfigure\nillustrates the environment structure created by evaluating the\nexpression\nsquare(5)\nin the\nprogram\nenvironment, where\nfunction\ngenerated in\nfigure.\n\nApplying the\nfunction\nresults in the creation of a new environment, labeled E1 in the figure, that\nbegins with a frame in which\nparameter for the\nfunction,\nis bound to the argument 5.\n\nNote that name\nThe pointer leading upward from this frame shows that the\nframe s enclosing environment is the\nprogram\nenvironment.\n\nThe\nprogram\nenvironment is chosen here, because this is the environment that is\nindicated as part of the\nfunction\nobject.\n\nWithin E1, we evaluate the body of the\nfunction,\n\n```javascript\nreturn x * x;.\n```", - "token_count": 304, - "has_code": true, + "content": "The\nfunction\nobject is a pair whose code specifies that the\nfunction\nhas one\nparameter, namely , and a\nfunction\nbody\nreturn x * x;.\n\nA new binding, which associates the\nfunction\nobject with the\nname\n, has been added\nto the program frame.\n\nEnvironment structure produced by evaluating function square(x) { return x * x; } in the program environment.\n\nIn general, const ,\nfunction , and\nlet\nadd bindings to frames.\n\nAssignment is forbidden on constants, so our environment model\nneeds to distinguish names that refer to constants\nfrom names that refer to variables.\n\nWe indicate that\na name is a constant by writing an equal sign after the colon\nthat follows the name.\n\nWe consider function declarations as equivalent to constant\ndeclarations;\nobserve the equal signs after the colons in\nfigure.\n\nNow that we have seen how\nfunctions\nare created, we can describe how\nfunctions\nare applied.\n\nThe environment model specifies: To apply a\nfunction\nto arguments, create a new environment containing a frame that binds the\nparameters to the values of the arguments.\n\nThe enclosing environment of\nthis frame is the environment specified by the\nfunction.\n\nNow, within this new environment, evaluate the\nfunction\nbody.\n\nTo show how this rule is followed,\nfigure\nillustrates the environment structure created by evaluating the\nexpression\nsquare(5)\nin the\nprogram\nenvironment, where is the\nfunction\ngenerated in\nfigure.\n\nApplying the\nfunction\nresults in the creation of a new environment, labeled E1 in the figure, that\nbegins with a frame in which , the\nparameter for the\nfunction,\nis bound to the argument 5.\n\nNote that name in environment E1 is followed by a colon with no equal sign, which indicates that the parameter is treated as a variable.", + "token_count": 288, + "has_code": false, "chapter": "Modularity, Objects, and State", "section": "The Environment Model of Evaluation", "subsection": "The Rules for Evaluation", @@ -4080,9 +5360,9 @@ "chunk_id": "Modularity_Objects_and_State_The_Rules_for_Evaluation_2" }, { - "content": "Within E1, we evaluate the body of the\nfunction,\n\nSince the value of 5 * 5, or 25.\n\n```javascript\nEnvironment created by evaluating\n\t square(5)\n\t in the program environment.\n```\n\nThe environment model of\nfunction\napplication can be summarized by two\nrules:\n-\n-\nA\nfunction\nobject is applied to a set of arguments by constructing a frame,\nbinding the parameters of the function\nto the arguments of the call, and then evaluating the body of the\nfunction\nin the context of the new environment constructed.\n\nThe new frame has as\nits enclosing environment the environment part of the\nfunction\nobject being applied.\n\nThe result of the application is the result of evaluating\nthe return expression of the first return statement encountered\nwhile evaluating the function body.\n-\n-\nA\nfunction\nis created by evaluating a\nlambda\nexpression relative to a given environment.\n\nThe resulting\nfunction\nobject is a pair consisting of the text of the\nlambda\nexpression and a pointer to the environment in which the\nfunction\nwas created.\n\n```javascript\nname=value\n\tin some environment\n\tlocates the binding of the name in the environment.\n\tThat is, one finds the first frame in the environment that contains a\n\tbinding for the name.\n\tIf the binding is a variable bindingindicated in the frame by\n\tjust :\n\tafter the namethat binding is changed to reflect the new\n\tvalue of the variable.\n\tOtherwise, if the binding in the frame is a constant\n\tbindingindicated\n\tin the frame by :=\n\tafter the namethe assignment signals an\n\t\"assignment to constant\" error.\n\tIf the name is unbound in the environment, then\n\tthe assignment signals a\n\t\"variable undeclared\" error.\n```\n\nThese evaluation rules, though considerably more complex than the\nsubstitution model, are still reasonably straightforward.\n\nMoreover,\nthe evaluation model, though abstract, provides a correct description\nof how the interpreter evaluates expressions.", - "token_count": 301, - "has_code": true, + "content": "Note that name in environment E1 is followed by a colon with no equal sign, which indicates that the parameter is treated as a variable.\n\nThe\nprogram\nenvironment is chosen here, because this is the environment that is\nindicated as part of the\nfunction\nobject.\n\nWithin E1, we evaluate the body of the\nfunction,\nreturn x * x;.\n\nSince the value of in E1 is 5, the result is\n5 * 5,\nor 25.\n\nEnvironment created by evaluating square(5) in the program environment.\n\nThe environment model of\nfunction\napplication can be summarized by two\nrules:\n-\n-\nA\nfunction\nobject is applied to a set of arguments by constructing a frame,\nbinding the parameters of the function\nto the arguments of the call, and then evaluating the body of the\nfunction\nin the context of the new environment constructed.\n\nThe new frame has as\nits enclosing environment the environment part of the\nfunction\nobject being applied.\n\nThe result of the application is the result of evaluating\nthe return expression of the first return statement encountered\nwhile evaluating the function body.\n-\n-\nA\nfunction\nis created by evaluating a\nlambda\nexpression relative to a given environment.\n\nThe resulting\nfunction\nobject is a pair consisting of the text of the\nlambda\nexpression and a pointer to the environment in which the\nfunction\nwas created.\n\nFinally, we specify the behavior of assignment, the operation that forced us to introduce the environment model in the first place.\n\nEvaluating the expression name=value in some environment locates the binding of the name in the environment.\n\nThat is, one finds the first frame in the environment that contains a binding for the name.", + "token_count": 277, + "has_code": false, "chapter": "Modularity, Objects, and State", "section": "The Environment Model of Evaluation", "subsection": "The Rules for Evaluation", @@ -4090,8 +5370,8 @@ "chunk_id": "Modularity_Objects_and_State_The_Rules_for_Evaluation_3" }, { - "content": "Moreover,\nthe evaluation model, though abstract, provides a correct description\nof how the interpreter evaluates expressions.\n\nIn chapter we shall\nsee how this model can serve as a blueprint for implementing a working\ninterpreter.\n\nThe following sections elaborate the details of the\nmodel by analyzing some illustrative programs.", - "token_count": 48, + "content": "That is, one finds the first frame in the environment that contains a binding for the name.\n\nOtherwise, if the binding in the frame is a constant bindingindicated in the frame by := after the namethe assignment signals an \"assignment to constant\" error.\n\nIf the name is unbound in the environment, then the assignment signals a \"variable undeclared\" error.\n\nThese evaluation rules, though considerably more complex than the\nsubstitution model, are still reasonably straightforward.\n\nMoreover,\nthe evaluation model, though abstract, provides a correct description\nof how the interpreter evaluates expressions.\n\nIn chapter we shall\nsee how this model can serve as a blueprint for implementing a working\ninterpreter.\n\nThe following sections elaborate the details of the\nmodel by analyzing some illustrative programs.", + "token_count": 122, "has_code": false, "chapter": "Modularity, Objects, and State", "section": "The Environment Model of Evaluation",